Почти каждый ресурс C ++, который я видел, где обсуждаются подобные вещи, говорит мне, что я должен предпочесть полиморфные подходы к использованию RTTI (идентификация типа во время выполнения). В общем, я серьезно отношусь к такому совету и постараюсь понять логику - в конце концов, C ++ - могущественный зверь, и его трудно понять во всей его глубине. Однако в этом конкретном вопросе я рисую пробел и хотел бы посмотреть, какие советы может предложить Интернет. Во-первых, позвольте мне резюмировать то, что я узнал до сих пор, перечислив общие причины, которые цитируются, почему RTTI «считается вредным»:
Некоторые компиляторы не используют его / RTTI не всегда включен
Я действительно не верю этому аргументу. Это все равно что сказать, что я не должен использовать возможности C ++ 14, потому что есть компиляторы, которые его не поддерживают. И все же никто не стал бы отговаривать меня от использования функций C ++ 14. Большинство проектов будут влиять на компилятор, который они используют, и на то, как он настроен. Даже цитируя справочную страницу gcc:
-fno-rtti
Отключите создание информации о каждом классе с виртуальными функциями для использования функциями идентификации типа времени выполнения C ++ (dynamic_cast и typeid). Если вы не используете эти части языка, вы можете сэкономить место, используя этот флаг. Обратите внимание, что обработка исключений использует ту же информацию, но G ++ генерирует ее по мере необходимости. Оператор dynamic_cast по-прежнему может использоваться для приведения типов, которые не требуют информации о типе времени выполнения, то есть приведения к "void *" или к однозначным базовым классам.
Это говорит мне о том, что если я не использую RTTI, я могу его отключить. Это все равно, что сказать, если вы не используете Boost, вам не нужно ссылаться на него. Мне не нужно планировать случай, когда кто-то компилирует с -fno-rtti
. Кроме того, в этом случае компилятор выйдет из строя.
Это требует дополнительной памяти / может быть медленным
Всякий раз, когда у меня возникает соблазн использовать RTTI, это означает, что мне нужно получить доступ к какой-то информации о типе или характеристике моего класса. Если я реализую решение, которое не использует RTTI, это обычно означает, что мне придется добавить некоторые поля в мои классы для хранения этой информации, поэтому аргумент памяти является своего рода недействительным (я приведу пример этого ниже).
На самом деле dynamic_cast может быть медленным. Однако обычно есть способы избежать использования его в критических для скорости ситуациях. И я не совсем вижу альтернативы. Этот ответ SO предлагает использовать перечисление, определенное в базовом классе, для хранения типа. Это работает, только если вы знаете все свои производные классы априори. Это довольно большое «если»!
Из этого ответа также следует, что стоимость RTTI также не ясна. Разные люди измеряют разные вещи.
Элегантный полиморфный дизайн сделает RTTI ненужным
К таким советам я отношусь серьезно. В этом случае я просто не могу придумать хорошие решения без RTTI, которые охватывают мой вариант использования RTTI. Приведу пример:
Скажем, я пишу библиотеку для обработки графиков каких-то объектов. Я хочу разрешить пользователям создавать свои собственные типы при использовании моей библиотеки (поэтому метод enum недоступен). У меня есть базовый класс для моего узла:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
Теперь мои узлы могут быть разных типов. Как на счет этих:
class red_node : virtual public node_base
{
public:
red_node();
virtual ~red_node();
void get_redness();
};
class yellow_node : virtual public node_base
{
public:
yellow_node();
virtual ~yellow_node();
void set_yellowness(int);
};
Черт, почему бы ни одного из этих:
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
Последняя функция интересна. Вот как это записать:
void orange_node::poke_adjacent_oranges()
{
auto adj_nodes = get_adjacent_nodes();
foreach(auto node, adj_nodes) {
// In this case, typeid() and static_cast might be faster
std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
if (o_node) {
o_node->poke();
}
}
}
Все это кажется ясным и чистым. Мне не нужно определять атрибуты или методы там, где они мне не нужны, базовый класс узла может оставаться скудным и средним. С чего мне начать без RTTI? Может быть, я могу добавить атрибут node_type к базовому классу:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
private:
std::string my_type;
};
Является ли std :: string хорошей идеей для типа? Может и нет, но что еще можно использовать? Придумайте число и надеетесь, что его еще никто не использует? Кроме того, в случае моего orange_node, что, если я хочу использовать методы из red_node и yellow_node? Придется ли мне хранить несколько типов на каждом узле? Это кажется сложным.
Вывод
Эти примеры не кажутся слишком сложными или необычными (я работаю над чем-то похожим в своей повседневной работе, где узлы представляют собой реальное оборудование, которое контролируется с помощью программного обеспечения, и которое делает совершенно разные вещи в зависимости от того, что они собой представляют). Тем не менее, я не знал бы чистого способа сделать это с помощью шаблонов или других методов. Обратите внимание, что я пытаюсь понять проблему, а не защищать свой пример. Мое чтение таких страниц, как ответ SO, на который я указал выше, и эта страница в Викиучебниках, похоже, наводят на мысль, что я злоупотребляю RTTI, но я хотел бы узнать, почему.
Итак, вернемся к моему первоначальному вопросу: почему «чистый полиморфизм» предпочтительнее использования RTTI?
источник
node_base
является частью библиотеки, и пользователи будут создавать свои собственные типы узлов. Тогда они не могут изменить,node_base
чтобы разрешить другое решение, поэтому, возможно, тогда RTTI станет их лучшим вариантом. С другой стороны, есть и другие способы спроектировать такую библиотеку, чтобы новые типы узлов могли вписываться в нее гораздо более элегантно, без необходимости использования RTTI (а также других способов разработки новых типов узлов).Ответы:
Интерфейс описывает то, что нужно знать, чтобы взаимодействовать в данной ситуации в коде. После того, как вы расширяете интерфейс с «всей иерархией типов», ваша интерфейс «площадь поверхности» становится огромным, что делает рассуждение о ней сложнее .
Например, ваш "тычок соседних апельсинов" означает, что я, как третья сторона, не могу подражать апельсину! Вы в частном порядке объявили оранжевый тип, а затем используете RTTI, чтобы ваш код вел себя по-особенному при взаимодействии с этим типом. Если я хочу «быть оранжевым», я должен быть в вашем частном саду.
Теперь каждый, кто соединяется с "оранжевым", соединяется со всем вашим оранжевым типом и неявно со всем вашим частным садом, а не с определенным интерфейсом.
Хотя на первый взгляд это выглядит как отличный способ расширить ограниченный интерфейс без необходимости изменять всех клиентов (добавлять
am_I_orange
), вместо этого обычно происходит окостенение кодовой базы и предотвращение дальнейшего расширения. Особая оранжевость становится неотъемлемой частью функционирования системы и не позволяет вам создать «мандариновую» замену апельсина, которая реализована по-другому и, возможно, удаляет зависимость или элегантно решает некоторые другие проблемы.Это означает, что вашего интерфейса должно быть достаточно для решения вашей проблемы. С этой точки зрения, почему вам нужно тыкать только апельсины, и если да, то почему апельсин был недоступен в интерфейсе? Если вам нужен нечеткий набор тегов, которые можно добавлять по отдельности, вы можете добавить его к своему типу:
Это обеспечивает аналогичное массовое расширение вашего интерфейса от узко заданного до широкого, основанного на тегах. За исключением того, что это делается через RTTI и детали реализации (также известные как «как вы реализованы? С оранжевым шрифтом? Хорошо, вы проходите»), он делает это с помощью чего-то, что легко эмулировать с помощью совершенно другой реализации.
Это можно распространить даже на динамические методы, если вам это нужно. «Поддерживаете ли вы, что вас обманывают аргументами Баз, Том и Элис? Хорошо, обманываю вас». В широком смысле это менее навязчиво, чем динамическое приведение, чтобы понять, что другой объект является типом, который вы знаете.
Теперь мандариновые объекты могут иметь оранжевый тег и подыгрывать, при этом они не связаны с реализацией.
Это все еще может привести к огромному беспорядку, но это по крайней мере беспорядок сообщений и данных, а не иерархии реализации.
Абстракция - это игра в разделение и сокрытие ненужных вещей. Это упрощает анализ кода локально. RTTI пробивает дыру прямо через абстракцию в деталях реализации. Это может упростить решение проблемы, но зато очень легко привязать вас к одной конкретной реализации.
источник
dynamic_cast
(RTTI), и в этом случае теги избыточны. Вторая возможность - класс бога - отвратительна. Подводя итог, в этом ответе много слов, которые, на мой взгляд, звучат хорошо для программистов на Java, но фактическое содержание бессмысленно.Большинство моральных убеждений против того или иного признака являются типичностью, основанными на наблюдении за некоторыми ошибочными представлениями. использования этой особенности.
Где моралисты неудачу в том , что они предполагают ВСЕ обычаи бытуют превратное мнение, в то время как на самом деле существуют особенности по причине.
У них есть то , что я имел обыкновение называть «Водопроводчик комплексом»: они думают , что все краны имеют неточности , потому что все краны они призваны на ремонт. На самом деле большинство кранов работают хорошо: для них просто не вызвать сантехника!
Сумасшедшая вещь, которая может случиться, когда, чтобы избежать использования данной функции, программисты пишут много шаблонного кода, фактически в частном порядке повторно реализуя именно эту функцию. (Вы когда-нибудь встречали классы, которые не используют ни RTTI, ни виртуальные вызовы, но имеют значение для отслеживания того, какой фактический производный тип они представляют? Это не более чем RTTI замаскированное переосмысление .)
Существует общий способ думать о полиморфизме:
IF(selection) CALL(something) WITH(parameters)
. (Извините, но программирование, если не считать абстракции, - это все)Использование полиморфизма времени разработки (концепций) во время компиляции (на основе шаблонного вывода), времени выполнения (на основе наследования и виртуальных функций) или на основе данных (RTTI и переключение), зависит от того, насколько известны решения. на каждом этапе производства и насколько они изменчивы в каждом контексте.
Идея в том, что:
чем больше вы можете ожидать, тем больше шансов обнаружить ошибки и избежать ошибок, влияющих на конечного пользователя.
Если все постоянно (включая данные), вы можете делать все с помощью метапрограммирования шаблонов. После того, как компиляция произошла с актуализированными константами, вся программа сводится только к оператору return, выдающему результат .
Если есть несколько случаев, которые все известны во время компиляции , но вы не знаете о фактических данных, с которыми они должны действовать, тогда полиморфизм времени компиляции (в основном CRTP или аналогичный) может быть решением.
Если выбор случаев зависит от данных (а не от известных значений времени компиляции) и переключение является одномерным (то, что нужно сделать, может быть уменьшено только до одного значения), то диспетчеризация на основе виртуальных функций (или вообще "таблицы указателей функций" ") нужно.
Если переключение является многомерным, поскольку нет родной множественной диспетчеризации среды выполнения в C ++ , вам необходимо либо:
Если не только переключение, но даже действия не известны во время компиляции, тогда требуются сценарии и синтаксический анализ : сами данные должны описывать действие, которое должно быть выполнено над ними.
Теперь, поскольку каждый из перечисленных мною случаев можно рассматривать как частный случай того, что следует за ним, вы можете решить каждую проблему, злоупотребляя самым нижним решением, а также для проблем, доступных для самого верхнего.
Вот что морализация на самом деле толкает избежать. Но это не означает, что проблем, живущих в самых нижних доменах, не существует!
Бить RTTI просто для того, чтобы его разбить, все равно что бить
goto
просто для того, чтобы разбить его. Вещи для попугаев, а не для программистов.источник
В небольшом примере это выглядит неплохо, но в реальной жизни вы скоро получите длинный набор типов, которые могут совать друг друга, причем некоторые из них, возможно, только в одном направлении.
А как насчет
dark_orange_node
, илиblack_and_orange_striped_node
, илиdotted_node
? Может на нем точки разного цвета? Что если большинство точек оранжевого цвета, тогда можно ли ткнуть?И каждый раз, когда вам нужно добавить новое правило, вам придется повторно посещать все
poke_adjacent
функции и добавлять дополнительные операторы if.Как всегда, сложно создать общие примеры, я вам это дам.
Но если бы я сделал этот конкретный пример, я бы добавил
poke()
члена ко всем классам и позволил бы некоторым из них игнорировать call (void poke() {}
), если они не заинтересованы.Конечно, это было бы даже дешевле, чем сравнивать
typeid
s.источник
supportsBehaviour
иinvokeBehaviour
каждый класс может иметь список поведений. Одним из вариантов поведения будет Poke, и он может быть добавлен в список поддерживаемых Behaviors всеми классами, которые хотят быть доступными.Я считаю, что вы неправильно поняли такие аргументы.
Есть ряд мест для кодирования C ++, в которых не следует использовать RTTI. Где переключатели компилятора используются для принудительного отключения RTTI. Если вы пишете код в рамках такой парадигмы ... то вы почти наверняка уже были проинформированы об этом ограничении.
Таким образом, проблема связана с библиотеками . То есть, если вы пишете библиотеку, которая зависит от RTTI, ваша библиотека не может использоваться пользователями, которые отключили RTTI. Если вы хотите, чтобы ваша библиотека использовалась этими людьми, тогда она не может использовать RTTI, даже если ваша библиотека также используется людьми, которые могут использовать RTTI. Не менее важно, что если вы не можете использовать RTTI, вам придется немного усерднее искать библиотеки, поскольку использование RTTI является для вас препятствием.
Есть много вещей, которые вы не делаете в горячих петлях. Вы не выделяете память. Вы не перебираете связанные списки. И так далее. RTTI, безусловно, может быть еще одним из тех, что «не делайте этого здесь».
Однако рассмотрите все ваши примеры RTTI. Во всех случаях у вас есть один или несколько объектов неопределенного типа, и вы хотите выполнить над ними некоторую операцию, которая может быть невозможна для некоторых из них.
Это то, над чем нужно работать на уровне дизайна . Вы можете писать контейнеры, которые не выделяют память, что соответствует парадигме «STL». Вы можете избежать структур данных связанных списков или ограничить их использование. Вы можете реорганизовать массивы структур в структуры массивов или что-то еще. Он меняет некоторые вещи, но вы можете разделить его на части.
Превратить сложную операцию RTTI в обычный вызов виртуальной функции? Это проблема дизайна. Если вам нужно это изменить, то это потребует изменений в каждом производном классе. Это меняет способ взаимодействия большого количества кода с различными классами. Возможности такого изменения выходят далеко за пределы критических для производительности участков кода.
Итак ... почему вы с самого начала написали неправильно?
С какой целью?
Вы говорите, что базовый класс «худощавый и средний». Но на самом деле ... его не существует . На самом деле он ничего не делает .
Просто посмотрите на ваш пример:
node_base
. Что это? Кажется, что это нечто, к которому примыкают другие вещи. Это интерфейс Java (к тому же предварительные обобщения Java): класс, который существует исключительно для того, чтобы быть чем-то, что пользователи могут преобразовывать в реальный тип. Возможно, вы добавите какую-то базовую функцию, например, смежность (Java добавляетToString
), но это все.Есть разница между «худым и скупым» и «прозрачным».
Как сказал Якк, такие стили программирования ограничивают себя с точки зрения взаимодействия, потому что, если все функциональные возможности находятся в производном классе, то пользователи вне этой системы, не имеющие доступа к этому производному классу, не могут взаимодействовать с системой. Они не могут переопределить виртуальные функции и добавить новое поведение. Они даже не могут вызывать эти функции.
Но что они также делают, так это делают серьезной проблемой действительно делать что-то новое, даже внутри системы. Подумайте о своей
poke_adjacent_oranges
функции. Что произойдет, если кому-то понадобитсяlime_node
тип, который можно тыкать точно так же, какorange_node
s? Ну, мы не можем извлечьlime_node
изorange_node
; это не имеет смысла.Вместо этого мы должны добавить новый
lime_node
производный отnode_base
. Затем измените имяpoke_adjacent_oranges
наpoke_adjacent_pokables
. А затем попробуйте выполнить приведение кorange_node
иlime_node
; какой бы бросок ни сработал, мы и тыкаем.Однако
lime_node
необходимо собственноеpoke_adjacent_pokables
. И эта функция должна выполнять те же проверки приведения.А если мы добавим третий тип, мы должны не только добавить его собственную функцию, но и изменить функции в двух других классах.
Очевидно, теперь вы делаете
poke_adjacent_pokables
бесплатную функцию, чтобы она работала для всех из них. Но что, как вы думаете, произойдет, если кто-то добавит четвертый тип и забудет добавить его к этой функции?Здравствуйте, тихая поломка . Программа работает более или менее нормально, но это не так. Если
poke
бы это была настоящая виртуальная функция, компилятор отказал бы, если бы вы не переопределили чистую виртуальную функцию изnode_base
.По-своему у вас таких проверок компилятора нет. Конечно, компилятор не будет проверять виртуальные файлы, не являющиеся чистыми, но, по крайней мере, у вас есть защита в тех случаях, когда защита возможна (например, нет операции по умолчанию).
Использование прозрачных базовых классов с RTTI приводит к кошмару обслуживания. Действительно, большинство случаев использования RTTI приводит к головной боли при обслуживании. Это не означает, что RTTI бесполезен (например, он жизненно важен для
boost::any
работы). Но это очень специализированный инструмент для очень специализированных нужд.В этом смысле он «вреден» так же, как
goto
. Это полезный инструмент, с которым нельзя отказываться. Но его использование в вашем коде должно быть редким .Итак, если вы не можете использовать прозрачные базовые классы и динамическое приведение типов, как избежать жирных интерфейсов? Как не допустить всплытия каждой функции, которую вы, возможно, захотите вызвать для типа, от всплытия до базового класса?
Ответ зависит от того, для чего предназначен базовый класс.
Прозрачные базовые классы вроде
node_base
просто используют неправильный инструмент для решения проблемы. Связанные списки лучше всего обрабатываются шаблонами. Тип узла и смежность будут предоставлены типом шаблона. Если вы хотите поместить в список полиморфный тип, вы можете. Просто используйтеBaseClass*
какT
в аргументе шаблона. Или ваш предпочтительный умный указатель.Но есть и другие сценарии. Один - это тип, который выполняет множество функций, но имеет несколько дополнительных частей. Один экземпляр может реализовывать определенные функции, а другой - нет. Однако конструкция таких типов обычно дает правильный ответ.
Класс "entity" - прекрасный пример этого. Этот класс издавна изводил разработчиков игр. Концептуально у него есть гигантский интерфейс, живущий на пересечении почти дюжины совершенно разных систем. И разные сущности имеют разные свойства. Некоторые объекты не имеют визуального представления, поэтому их функции визуализации ничего не делают. И все это определяется во время выполнения.
Современное решение для этого - система компонентного типа.
Entity
это просто контейнер из набора компонентов, между которыми есть клей. Некоторые компоненты не являются обязательными; объект, не имеющий визуального представления, не имеет «графического» компонента. Сущность без ИИ не имеет "контролирующего" компонента. И так далее.Сущности в такой системе - это просто указатели на компоненты, причем большая часть их интерфейса обеспечивается прямым доступом к компонентам.
Разработка такой компонентной системы требует признания на этапе проектирования того, что определенные функции концептуально сгруппированы вместе, так что все типы, реализующие одну, будут реализовывать их все. Это позволяет выделить класс из предполагаемого базового класса и сделать его отдельным компонентом.
Это также помогает следовать принципу единой ответственности. Такой компонентный класс несет ответственность только за то, чтобы быть держателем компонентов.
От Мэтью Уолтона:
Хорошо, давайте исследуем это.
Чтобы это имело смысл, вам потребуется ситуация, когда некоторая библиотека L предоставляет контейнер или другой структурированный держатель данных. Пользователь может добавлять данные в этот контейнер, перебирать его содержимое и т. Д. Однако библиотека на самом деле ничего не делает с этими данными; он просто управляет своим существованием.
Но он управляет даже не столько своим существованием, сколько своим разрушением . Причина в том, что если вы ожидаете использовать RTTI для таких целей, вы создаете классы, о которых L не знает. Это означает, что ваш код выделяет объект и передает его L для управления.
Теперь есть случаи, когда что-то подобное является законным дизайном. Сигнализация событий / передача сообщений, потокобезопасные рабочие очереди и т. Д. Общий шаблон здесь таков: кто-то выполняет сервис между двумя частями кода, который подходит для любого типа, но сервис не должен знать о конкретных задействованных типах .
В языке C этот шаблон пишется по буквам
void*
, и его использование требует особой осторожности, чтобы избежать нарушения. В C ++ этот шаблон пишетсяstd::experimental::any
(скоро будет написаноstd::any
).Это должно работать следующим образом: L предоставляет
node_base
класс, который принимаетany
ваши фактические данные. Когда вы получаете сообщение, рабочий элемент очереди потока или что-то еще, вы затем приводитеany
его к соответствующему типу, который известен как отправителю, так и получателю.Таким образом , вместо того , чтобы извлечения
orange_node
изnode_data
, вы просто придерживаться вorange
внутриnode_data
«Sany
области члена. Конечный пользователь извлекает его и используетany_cast
для преобразования вorange
. Если бросок не удастся, значит, это не такorange
.Теперь, если вы хоть немного знакомы с реализацией
any
, вы, скорее всего, скажете: «Эй, погоди минутку:any
внутренне использует RTTI дляany_cast
работы». На что я отвечаю "... да".В этом суть абстракции . Глубоко в деталях кто-то использует RTTI. Но на том уровне, на котором вы должны работать, прямой RTTI - это не то, что вам следует делать.
Вы должны использовать типы, которые предоставляют вам желаемую функциональность. В конце концов, вам действительно не нужен RTTI. Вам нужна структура данных, которая может хранить значение данного типа, скрывать его от всех, кроме желаемого места назначения, а затем преобразовывать обратно в этот тип с проверкой того, что сохраненное значение действительно относится к этому типу.
Это называется
any
. Он использует RTTI, но его использованиеany
намного превосходит использование RTTI напрямую, поскольку оно более точно соответствует желаемой семантике.источник
Если вы вызываете функцию, как правило, вам все равно, какие точные шаги она предпримет, а только то, что какая-то цель более высокого уровня будет достигнута в определенных ограничениях (и то, как функция делает это, на самом деле является вашей проблемой).
Когда вы используете RTTI для предварительного выбора специальных объектов, которые могут выполнять определенную работу, в то время как другие в том же наборе не могут, вы нарушаете этот удобный взгляд на мир. Внезапно вызывающий должен знать, кто и что может делать, вместо того, чтобы просто сказать своим миньонам, чтобы они продолжали. Некоторых это беспокоит, и я подозреваю, что это большая часть причины, по которой RTTI считается немного грязным.
Есть проблемы с производительностью? Может быть, но я никогда не сталкивался с этим, и это может быть мудрость двадцатилетней давности или людей, которые искренне считают, что использование трех инструкций по сборке вместо двух недопустимо.
Итак, как с этим бороться ... В зависимости от вашей ситуации может иметь смысл объединить любые специфичные для узла свойства в отдельные объекты (т.е. весь «оранжевый» API может быть отдельным объектом). Тогда у корневого объекта может быть виртуальная функция для возврата «оранжевого» API, возвращающего nullptr по умолчанию для не оранжевых объектов.
Хотя это может быть излишним в зависимости от вашей ситуации, это позволит вам запрашивать на корневом уровне, поддерживает ли конкретный узел определенный API, и если да, выполнять функции, специфичные для этого API.
источник
C ++ построен на идее проверки статического типа.
[1] RTTI, то есть
dynamic_cast
иtype_id
, это проверка динамического типа.Итак, по сути, вы спрашиваете, почему проверка статического типа предпочтительнее проверки динамического типа. И простой ответ: будет ли проверка статического типа предпочтительнее проверки динамического типа, зависит от этого . О многом. Но C ++ - один из языков программирования, созданных на основе идеи проверки статического типа. А это означает, что, например, процесс разработки, в частности тестирования, обычно адаптируется к проверке статического типа, а затем подходит для этого лучше всего.
Re
вы можете сделать этот процесс-гетерогенные-узлы-графа с проверкой статического типа и без какого-либо преобразования с помощью шаблона посетителя, например, вот так:
Это предполагает, что известен набор типов узлов.
Когда это не так, шаблон посетителя (существует множество его вариаций) может быть выражен с помощью нескольких централизованных приведений или всего лишь одного.
Для подхода на основе шаблонов см. Библиотеку Boost Graph. К сожалению, я не знаком с ним, я не использовал его. Поэтому я не совсем уверен, что он делает и как, и в какой степени он использует проверку статического типа вместо RTTI, но поскольку Boost обычно основан на шаблоне, а проверка статического типа является центральной идеей, я думаю, вы обнаружите, что его подбиблиотека Graph также основана на проверке статического типа.
[1] Информация о типе времени выполнения .
источник
Конечно, есть сценарий, в котором полиморфизм не может помочь: имена.
typeid
позволяет получить доступ к имени типа, хотя способ кодирования этого имени определяется реализацией. Но обычно это не проблема, так как вы можете сравнить дваtypeid
-s:То же самое и с хешами.
вредно , безусловно, завышено: у RTTI есть недостатки, но есть имеет преимущество.
На самом деле вам не обязательно использовать RTTI. RTTI - это инструмент для решения проблем ООП: если вы воспользуетесь другой парадигмой, они, скорее всего, исчезнут. C не имеет RTTI, но все еще работает. Вместо этого C ++ полностью поддерживает ООП и предоставляет несколько инструментов для решения некоторых проблем, которые могут потребовать информации во время выполнения: одним из них действительно является RTTI, хотя и имеет свою цену. Если вы не можете себе это позволить, то, что вам лучше сказать, только после безопасного анализа производительности, все еще существует старая школа
void*
: это бесплатно. Без затрат. Но вы не получаете никакой безопасности типов. Так что все дело в сделках.Если вы пишете (возможно, строго) соответствующий код C ++, вы можете ожидать такого же поведения независимо от реализации. Соответствующие стандартам реализации должны поддерживать стандартные функции C ++.
Но учтите, что в некоторых средах, определяемых C ++ («автономных»), RTTI не требуется, как и исключения,
virtual
и так далее. Для правильной работы RTTI необходим нижележащий уровень, который имеет дело с низкоуровневыми деталями, такими как ABI и фактическая информация о типе.Я согласен с Якком относительно RTTI в этом случае. Да, его можно было использовать; но логически ли это правильно? Тот факт, что язык позволяет обойти эту проверку, не означает, что ее нужно делать.
источник