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

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

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

person xcrypt    schedule 03.03.2012    source источник
comment
Где кто сказал, что это плохой дизайн?   -  person Seth Carnegie    schedule 03.03.2012
comment
@SethCarnegie Я слышал, как некоторые люди говорят это на SO, а также читал в книге, что проверка типов - это плохой дизайн, но причина не указана.   -  person xcrypt    schedule 03.03.2012
comment
То, что они не назвали причин, вероятно, свидетельствует о законности их требований. Некоторые люди говорят, что если вы выполняете много проверок типов, вы должны реорганизовать свой код в вызовы полиморфных функций. Например, вместо выполнения if (typeid(a) == atype) doa(); else if (typeid(a) == btype) dob(); else if (typeid(a) == ctype) doc(); вы можете просто выполнить a->doafunc();, и соответствующая виртуальная функция будет вызвана для типа среды выполнения a.   -  person Seth Carnegie    schedule 03.03.2012
comment
Не могли бы вы описать случаи, когда вы считаете, что это полезно?   -  person Vaughn Cato    schedule 03.03.2012
comment
хорошее и плохое субъективно, знаете ли...   -  person SigTerm    schedule 03.03.2012
comment
@Seth: Вы виновны в классической ошибке ad hominem. Тот факт, что утверждение сделано карго-культовым программистом, который не может его объяснить, не делает его ложным. Это ДЕЙСТВИТЕЛЬНО вызывает подозрение, но любое неподтвержденное утверждение, независимо от источника. Однако это требование правомерно; см. мой ответ.   -  person Ben Voigt    schedule 03.03.2012
comment
@BenVoigt заметил вероятно. Если и есть что-то, в чем я не виноват, так это то, что я все знаю. Кроме того, это не ad hominem, потому что я не нападаю на мотивы или характер какого-либо человека и не апеллирую к эмоциям, а не к логике. Суть моих комментариев в том, что ничто не может быть плохим дизайном всегда и навсегда само по себе. Вы можете использовать что-то правильно или неправильно, но это зависит от вас, и нет функции, которую никогда нельзя использовать. Меня раздражает, когда люди используют ложные массовые общие обобщения.   -  person Seth Carnegie    schedule 03.03.2012


Ответы (3)


Проблема не в 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 фактически обходят иерархию классов, поэтому время, необходимое им для поиска типа, пропорционально количеству классов между реальным типом и заданным типом. Каждая реализация будет отличаться, но это довольно хороший показатель того, что, возможно, вам не следует помещать его в критически важный для производительности код.

person Nicol Bolas    schedule 03.03.2012
comment
typeid(x).name() также очень удобен для написания операторов отладки/журнала. - person edA-qa mort-ora-y; 03.03.2012
comment
Не могли бы вы дать ссылку на то, где boost::any использует typeid? - person Ben Voigt; 03.03.2012
comment
@BenVoigt: я не знаю, где в сети находится исходный код boost::any, но он есть в строке 180 boost/any.hpp в версии 1.47. Действительно, простой поиск этого файла покажет несколько вариантов его использования. - person Nicol Bolas; 03.03.2012
comment
Хммм, это здесь. Но это использование на самом деле выглядит несколько неправильным, вы не можете поместить объект Derived и привести к Base*. Я предполагаю, что это ограничение boost::any исключительно из-за недостатка typeid, но вряд ли это доказывает, что typeid является правильным решением проблемы. - person Ben Voigt; 03.03.2012
comment
@BenVoigt: Но это использование на самом деле выглядит несколько нарушенным, вы не можете поместить объект Derived и привести его к Base*. Сломанный в глазах смотрящего. Недопустимо приводить Derived* к void*, а затем приводить к Base*. Так что с boost::any это тоже не законно. Для меня это boost::any выполнение своей работы: вы получаете обратно точно и только то, что вложили в него. - person Nicol Bolas; 03.03.2012
comment
@NicolBolas, я не хочу выглядеть очень глупо, но я не совсем понял твой пример. Следует ли использовать pType->Func() вместо предоставленного вами кода? Если нет, не могли бы вы указать это более четко для меня? Спасибо. - person rightaway717; 27.11.2014
comment
@edA-qamort-ora-y, я делал именно это прямо сейчас и задавался вопросом, хорошая это идея или нет. :) - person Duck Dodgers; 30.07.2019

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

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

person ruakh    schedule 03.03.2012
comment
И с точки зрения производительности, есть ли какие-то причины? Медленна ли проверка типов? - person xcrypt; 03.03.2012
comment
@xcrypt, если операнд typeid не является полиморфным типом, typeid выполняется во время компиляции. Если операнд является полиморфным типом, то, насколько он быстрый или медленный, зависит от того, как его реализовали разработчики компилятора. Если они реализовали это, заставив программу отправлять текст вашей программы сотруднику компании и заставлять его вводить тип класса обратно в программу, то это будет медленно. - person Seth Carnegie; 03.03.2012
comment
@SethCarnegie, кто бы сделал такое? :п - person xcrypt; 03.03.2012
comment
@xcrypt: как говорит Сет Карнеги, это зависит от реализации; но в целом я бы не ожидал, что производительность typeid будет сильно отличаться от производительности виртуальных функций. Так что это проблема разработки программного обеспечения, а не проблема производительности. - person ruakh; 03.03.2012

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

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

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

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

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

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

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

person Ben Voigt    schedule 03.03.2012
comment
Нет причин использовать typeid, если статический тип выражения не является полиморфным. Если только вы не пишете boost::any и не хотите гарантировать пользователю, что статический тип, который они вставили в переменную, — это тот же самый тип, который они пытаются из нее извлечь. Вы знаете, 99% пункта boost::any. - person Nicol Bolas; 03.03.2012
comment
@Nicol: IMO, это одно из применений отладки, а any_cast даже с самого начала неправильно обрабатывает типы. Все проблемы с typeid переносятся на boost::any, и в результате программы, использующие boost::any, сложно поддерживать и тестировать. - person Ben Voigt; 03.03.2012
comment
Это не имеет смысла. boost::any является типобезопасным void*. Его работа состоит в том, чтобы убедиться, что человек, который поместил что-то в any, и человек, который взял что-то из any, договорились о том, что это за что-то, чтобы не нарушать C++ и не вызывать неопределенное поведение. В этом нет ничего сложного для поддержки и тестирования; что вы вкладываете, то и получаете. Замена Лисков и Open-Closed неуместны в этом контексте. Конечно, существует сильная связь, но это то, о чем вы просили, когда решили использовать стирание шрифта. По крайней мере, это безопасная сильная связь. - person Nicol Bolas; 03.03.2012
comment
@Nicol: Но вы теряете ремонтопригодность, когда имеете сильную связь и нарушаете SOLID. И если комментарии не будут исключительно хорошими, использование наследования таким образом, который не соответствует LSP, может запутать будущих разработчиков, знакомых с ООП. И хотя boost::any может предотвратить небезопасные операции, он также запрещает повышающее преобразование, что теоретически правильно. Говорить, что void* действует так же, бессмысленно, поскольку void* изначально не имеет желаемой семантики. - person Ben Voigt; 03.03.2012
comment
Семантика void*s является желаемой семантикой для any. В общем, вы используете any, потому что вы не можете передать полностью типизированный объект через определенное место. Общее сообщение и сигналы, например. В этом случае вы просто соглашаетесь с делокализованной связью, потому что преимущества (возможность фактически работать) перевешивают недостатки (возможность получения исключения any_cast). Здорово жить в башне из слоновой кости SOLID и все такое, но иногда приходится делать не очень приятные вещи и соглашаться на повышенную стоимость обслуживания. Хорошо иметь any для облегчения обслуживания - person Nicol Bolas; 03.03.2012
comment
@Nicol: С этой точки зрения boost::any - это просто void* плюс проверка отладки. И отладка — это то, где, как я сказал, typeid может быть полезно. Это случай, о котором я упоминал, когда полиморфизм уже нарушен. - person Ben Voigt; 03.03.2012