Почему использование плохого дизайна ключевого слова typeid?

Я слышал, как многие говорят, что использование typeid плохой дизайн, но для меня кажется, что он предлагает большую полезность.

  • Когда (и почему) используется typeid "плохой дизайн"?
  • Когда используется typeid приемлемый?
  • Когда это неприемлемо, но вам все равно нужно что-то подобное, что была бы альтернатива, которая имела бы хороший дизайн?

Ответ 1

Проблема не в typeid. Проблема в том, что просмотр typeid побудил бы вас написать это:

PolymorphicType *pType = ...;
if(typeid(*pType) == typeid(Derived1))
  pType->Func1();
else if(typeid(*pType) == typeid(Derived2))
  pType->Func2();
else if(typeid(*pType) == typeid(Derived3))
  pType->Func3();

Это то, что мы называем "действительно глупым". Это вызов виртуальной функции, выполненный в максимально возможной степени. typeid имеет потенциал для злоупотребления при использовании для замены функций dynamic_cast и virtual.

Этот пример может показаться надуманным. В конце концов, очевидно, что это просто виртуальный вызов. Но плохой код часто растет с пути наименьшего сопротивления; все, что требуется, - это один человек сделать typeid() == typeid(), и семя этого кода начнется. В общем, если вы часто используете typeid часто, шансы очень хороши, что вы делаете то, что лучше сделать с помощью других языковых конструкций.

typeid - метод вывода метода полиморфного типа в крайнем случае.

Является ли использование typeid неправильным? Конечно нет. boost::any было бы невозможно без него. Ну, это было бы возможно, но это было бы не безопаснее, чем void*. typeid - это то, что делает возможным стирание типа boost::any. Существуют и другие законные способы его использования.

Но в отношении кодовых строк к использованию я бы предположил, что это должно быть примерно в одном из 10 000 строк кода, самое большее. Гораздо меньше этого, и вы, вероятно, используете его неправильно.

Проверяется ли проверка типа?

В общем, основной причиной вызова typeid является либо шаблонный код (как в boost::any), либо когда вы ожидаете полиморфного типа. Если тип статически определен (т.е. Задано имя или значение неполиморфного типа), вы можете ожидать, что это будет сделано во время компиляции.

Полиморфные ценности, о которых вы должны беспокоиться. Я видел тест производительности, который показал, что некоторые реализации typeid фактически проходят иерархию классов, поэтому время, необходимое для поиска типа, пропорционально количеству классов между реальным типом и данным типом. Каждая реализация будет отличаться, но это довольно хороший признак того, что, возможно, вы не должны ставить ее в критическом по производительности коде.

Ответ 2

Я не думаю, что кто-то говорит, что использование typeid само по себе плохой дизайн; скорее, вы услышите, что использование typeid свидетельствует о плохом дизайне. Идея заключается в том, что любая логика, которая отличает (скажем) Square от Circle, должна фактически находиться в этих классах, поэтому должна быть выражена виртуальной функцией (полиморфизмом).

Излишне говорить, что это не абсолютное правило.

Ответ 3

Слишком плохо, что те, кто дал вам этот хороший совет, не могли объяснить причины этого. Это означает, что они просто сторонники культа культуры.

Все выражения имеют статический тип времени компиляции (после расширения шаблона). Все типы либо полиморфны, либо нет.

Я попытаюсь объяснить вам, почему typeid (самое близкое) бесполезно в обоих случаях:

  • Нет причин использовать typeid, когда выражение static type не является полиморфным. Компилятор будет использовать статический тип, а не динамический тип, поэтому работает перегрузка функции времени компиляции. И идентификатор типа можно проверить гораздо более эффективно, используя шаблонный тип, например is_same.

  • Если статический тип выражения является полиморфным, то typeid терпит неудачу. Да, он даст информацию об динамическом типе объекта. Но только самый производный динамический тип. Это нарушает Принцип замещения Лискова, поскольку заданные типы Base и Child, добавление нового типа Grandchild, полученного из Child, не будет рассматриваться как Child. Принцип Open-Closed также нарушен, так как дизайн не может быть расширен новыми типами без перезаписи всего. Инкапсуляции нет, так как информация об иерархии классов должна быть разбросана по всей программе, создавая сильную связь. dynamic_cast преодолевает некоторые, но не все из этих проблем.

Короче говоря, программа, использующая typeid, будет очень трудно поддерживать и тестировать.

И, как и в случае с любым правилом, может быть ограниченное исключение. Где вы используете функции языка OO для наследования и виртуальных функций, но на самом деле вам не нужен полиморфный дизайн. Я могу думать точно о том, где вы хотите записать имя типа в виде строки, используя type_info::name(). Вы также можете использовать его для обнаружения среза (где полиморфизм уже нарушен). Но это хорошо для отладки только, никогда для управления потоком программы.