Майже кожен ресурс C ++, який я бачив, який обговорює подібне, говорить про те, що я повинен віддавати перевагу поліморфним підходам до використання RTTI (ідентифікація типу виконання). Взагалі, я сприймаю подібну пораду серйозно, і спробую зрозуміти обґрунтування - адже C ++ - це могутній звір і важко зрозуміти його на всю глибину. Однак для цього конкретного питання я малюю бланк і хотів би побачити, яку пораду може запропонувати Інтернет. По-перше, дозвольте підсумувати те, що я дізнався до цього часу, перерахувавши загальні причини, які цитуються, чому RTTI "вважається шкідливим":
Деякі компілятори не використовують його / RTTI не завжди вмикається
Я справді не купую цей аргумент. Це як би сказати, що я не повинен використовувати функції C ++ 14, оскільки там є компілятори, які не підтримують його. І все ж, ніхто не заважав би мені використовувати функції C ++ 14. Більшість проектів матиме вплив на компілятор, який вони використовують, і на те, як він налаштований. Навіть цитуючи сторінку gcc:
-fno-rtti
Вимкнути генерування інформації про кожен клас з віртуальними функціями для використання функціями ідентифікації типу C ++ (за часом виконання) (dynamic_cast та typeid). Якщо ви не використовуєте ці частини мови, ви можете заощадити місце, використовуючи цей прапор. Зауважте, що обробка виключень використовує ту саму інформацію, але G ++ генерує її за потребою. Оператор динамічної передачі все ще може використовуватися для лицьових даних, які не потребують інформації про тип часу виконання, тобто касти до "недійсного *" або для однозначних базових класів.
Це мені говорить, що якщо я не використовую RTTI, я можу його відключити. Це як би сказати, якщо ви не використовуєте Boost, вам не доведеться посилатися на нього. Мені не потрібно планувати випадок, коли хтось збирається -fno-rtti
. Плюс у цьому випадку компілятор вийде з ладу голосно і чітко.
Це коштує додаткової пам'яті / Може бути повільним
Кожного разу, коли я спокушаюсь використовувати RTTI, це означає, що мені потрібно отримати доступ до якоїсь типової інформації або риси мого класу. Якщо я реалізую рішення, яке не використовує RTTI, це зазвичай означає, що мені доведеться додати кілька класів до моїх класів, щоб зберігати цю інформацію, тому аргумент пам'яті є недійсним (я наведу приклад цього далі).
Дійсна передача може бути повільною. Зазвичай існують способи уникнути необхідності використання в ньому критичних для швидкості ситуацій. І я не зовсім бачу альтернативи. Ця відповідь ТА пропонує використовувати enum, визначений у базовому класі, для зберігання типу. Це працює лише в тому випадку, якщо ви знаєте всі похідні класи a-priori. Це досить велике "якщо"!
З цієї відповіді також видається, що вартість RTTI також не зрозуміла. Різні люди вимірюють різні речі.
Елегантні поліморфні конструкції зроблять RTTI непотрібним
Це такий тип порад, який я сприймаю серйозно. У цьому випадку я просто не можу придумати хороших не-RTTI-рішень, які стосуються мого випадку використання RTTI. Наведіть приклад:
Скажіть, я пишу бібліотеку для обробки графіків якихось об'єктів. Я хочу дозволити користувачам генерувати власні типи під час використання моєї бібліотеки (тому метод перерахування недоступний). У мене базовий клас для мого вузла:
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? Чи потрібно мені зберігати кілька типів на вузол? Це здається складним.
Висновок
Цей приклад не здається надто складним або незвичним (я працюю над чимось подібним у своїй щоденній роботі, де вузли представляють фактичне обладнання, яке контролюється за допомогою програмного забезпечення, і які роблять дуже різні речі залежно від того, що вони є). Але я б не знав чистого способу зробити це за допомогою шаблонів чи інших методів. Зверніть увагу, що я намагаюся зрозуміти проблему, а не захищати свій приклад. Моє читання сторінок, таких як відповідь ТА, яку я зв'язав вище, і ця сторінка у Вікікнижках, здається, підказує, що я зловживаю RTTI, але я хотів би дізнатися чому.
Отже, повернутися до мого початкового запитання: Чому перевагу «чистого поліморфізму» краще використовувати RTTI?
node_base
є частиною бібліотеки, і користувачі будуть робити власні типи вузлів. Тоді вони не можуть змінюватись, node_base
щоб дозволити інше рішення, тому, можливо, RTTI тоді стане їх найкращим варіантом. З іншого боку, є й інші способи проектування такої бібліотеки, щоб нові типи вузлів могли вміщуватися набагато більш елегантно, не потребуючи використання RTTI (та інших способів проектування нових типів вузлів).