Почти каждый ресурс 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 (а также других способов разработки новых типов узлов).