Почти каждый ресурс С++, который я видел, который обсуждает это, говорит мне, что я должен предпочесть полиморфные подходы к использованию RTTI (идентификация типа времени выполнения). В общем, я отношусь к этому совету серьезно, и попытаюсь понять его обоснование. В конце концов, С++ - могучий зверь и его трудно понять в полной мере. Однако для этого конкретного вопроса я рисую пробел и хотел бы посмотреть, какие советы может предложить интернет. Во-первых, позвольте мне обобщить то, что я узнал до сих пор, перечисляя общие причины, которые цитируются, почему RTTI "считается вредным":
Некоторые компиляторы не используют его /RTTI не всегда включен
Я действительно не покупаю этот аргумент. Мне нравится говорить, что я не должен использовать возможности С++ 14, потому что там есть компиляторы, которые его не поддерживают. И все же никто не будет препятствовать мне использовать возможности С++ 14. Большинство проектов будут иметь влияние на компилятор, который они используют, и как он настроен. Даже цитируя gcc manpage:
-fno-rtti
Отключить генерацию информации о каждом классе с помощью виртуальных функций для использования функциями идентификации типа времени выполнения С++ (dynamic_cast и typeid). Если вы не используете эти части языка, вы можете сэкономить некоторое пространство, используя этот флаг. Обратите внимание, что исключение обработка использует ту же информацию, но g++ генерирует ее по мере необходимости. Оператор dynamic_cast все еще может использоваться для приведений, которые не требуют информацию типа времени выполнения, то есть отбрасывается на "void *" или на однозначные базовые классы.
Что мне говорит, что если я не использую RTTI, я могу отключить его. Это как сказать, если вы не используете Boost, вам не нужно ссылаться на него. Мне не нужно планировать случай, когда кто-то компилируется с помощью -fno-rtti
. Кроме того, компилятор в этом случае не будет громким и ясным.
Он требует дополнительной памяти/может быть медленным
Всякий раз, когда я испытываю соблазн использовать RTTI, это означает, что мне нужно получить доступ к информации о типе или характеристике моего класса. Если я реализую решение, которое не использует RTTI, это обычно означает, что мне нужно будет добавить некоторые поля в мои классы для хранения этой информации, поэтому аргумент памяти будет недействительным (я приведу пример этого ниже).
Динамический_cast может быть медленным. Однако обычно есть способы избежать использования быстродействующих ситуаций. И я не совсем понимаю альтернативу. Этот ответ SO предлагает использовать перечисление, определенное в базовом классе, для хранения типа. Это работает, только если вы знаете все ваши производные классы априори. Это довольно большое "если"!
Из этого ответа также кажется, что стоимость RTTI также не ясна. Различные люди измеряют разные вещи.
Элегантные полиморфные конструкции сделают RTTI ненужным
Это тот совет, который я воспринимаю всерьез. В этом случае я просто не могу придумать хорошие решения, отличные от RTTI, которые охватывают мой вариант использования RTTI. Позвольте мне привести пример:
Скажем, я пишу библиотеку для обработки графиков каких-то объектов. Я хочу разрешить пользователям создавать свои собственные типы при использовании моей библиотеки (поэтому метод enum недоступен). У меня есть базовый класс для моего node:
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();
}
}
}
Все это кажется ясным и чистым. Мне не нужно определять атрибуты или методы, в которых они мне не нужны, базовый класс node может оставаться скудным и средним. Без 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? Должен ли я хранить несколько типов на node? Это кажется сложным.
Заключение
Эти примеры не кажутся чрезмерно сложными или необычными (я работаю над чем-то подобным в моей дневной работе, где узлы представляют собой реальное оборудование, которое контролируется через программное обеспечение, и которые делают совсем другую вещь в зависимости от того, что они представляют). Но я не знаю, как это сделать с помощью шаблонов или других методов. Обратите внимание, что я пытаюсь понять проблему, а не защищать свой пример. Мое чтение страниц, таких как ответ SO, который я связал выше, и эта страница в Wikibooks, кажется, предполагает, что я злоупотребляю RTTI, но я бы хотел чтобы узнать почему.
Итак, вернемся к моему первоначальному вопросу: Почему "чистый полиморфизм" предпочтительнее использования RTTI?