Я слышал о вариантах с dynamic_cast
& RTT (но стоит дорого) или виртуальные функции, но я не уверен, что лучше!
Допустим, у меня есть следующий пример
У меня есть родительский класс Human
с 2 детскими классами Man
а также Woman
,
Human
ofc предоставляет несколько стандартных методов.
Но давайте скажем Woman
имеет разные и более методы, чем Man
,
Почему мне нужно знать экземпляр объекта?
Допустим, у меня есть функция, которая позволяет людям только в качестве параметра.
Таким параметром может быть мужчина или женщина.
Но для дальнейших действий мне нужно знать, является ли человеческий объект на самом деле Man
или же Woman
Примените это к одному из них обоих.
Я думаю, что в этом случае не стоит использовать виртуальные функции, потому что у дочернего класса разное количество методов ?!
Вы правы — если вам понадобится кастинг или RTTI, это хороший признак того, что, возможно, вам нужно переосмыслить свой дизайн.
Но вся идея «наследования» заключается в том, что вы можете определить класс «мужчина» со специализированными методами и данными, которые относятся только к «мужчинам», другой класс «женщина» со своими собственными специализированными данными и методами … и родителем класс «человек» для тех классов, у них есть общие методы.
Кроме того, для клика обычно следует использовать класс «человек» (как правило, ему все равно, является ли экземпляр «мужчиной» или «женщиной»). Клиент должен знать как немного об экземпляре класса, насколько это возможно — достаточно, чтобы сделать его работу; больше не надо.
Наконец, если «мужчина» и «женщина» делают одну и ту же операцию, но нужно сделать это по-другому … вот где приходят виртуальные методы.
Эти ссылки могут помочь:
Вы делаете это так:
switch (human->gender) {
case GenderMan:
doTheManlyThing((Man*)human);
break;
case GenderWoman:
doTheWomanlyThing((Woman*)human);
break;
default:
abort("Unknown gender, stop the world");
}
С-совместимый синтаксис используется намеренно. Если вы собираетесь делать это так, как мы когда-то возвращались, почему бы не пойти на все деньги?
Если вы используете typeid(*human)
вместо индикатора интегрального типа, или if (dynamic_cast<whatewer*>) ...
как это делают сегодняшние дети, это концептуально то же самое, только немного (или намного) медленнее.
Альтернатива конечно
human->doTheHumanThing(); // a call to a virtual function
// possibly with an empty body
// in case one of the genders
// "has more actions" than the other(s)
но настоящие программисты не используют виртуальные функции. Они включают идентификаторы типов, и они нравится.
Другой альтернативой является Шаблон посетителя, который снова представляет собой набор виртуальных функций, которые, так сказать, располагаются только горизонтально, а не вертикально. Я не хочу, чтобы этот ответ стал 11521273-м описанием Посетителя, поэтому я просто отсылаю вас к универсальный шрифт мудрости.
Я на самом деле нашел другое решение.
Фреймворк, который я использую, на самом деле предоставляет решение для этого.
если (object-> GetClass () -> IsChildOf (APlayerCharacter :: StaticClass ()))
Это на самом деле мне очень помогает!
Если вы оказались парализованы анализом, помните правило преждевременная оптимизация (т.е. не делай этого). Просто покончим с этим. Вы можете выбрать другой путь позже по дороге (только не ждите слишком долго). 🙂
В каждом посте я читал, что dynamic_cast имеет свою стоимость. Я не
проверил это —
Существует простое решение для этого типа беспокойства, и это базовый набор навыков программирования: Проверьте это сами.
Что касается вашего беспокойства (в комментариях) по поводу производительности RTTI / dynamic_cast: также примените правило преждевременная оптимизация. Вызовы виртуальных функций настолько относительно быстры, что вам не следует об этом беспокоиться, если только вы не заметили проблему. Если у вас нет доказательств проблемы с производительностью, у вас действительно есть проблемы с производительностью? Бьярн Страуструп упоминает в одной из своих книг по С ++ сомнение, которое люди испытывали в отношении производительности виртуальной диспетчеризации. В настоящее время тесты показывают, что 50 000 000 виртуальных диспетчерских вызовов занимают меньше секунды. В моем профилировании виртуальный вызов по производительности практически идентичен не виртуальному вызову. Накладные расходы — это статистический шум (во всех случаях, кроме самых редких).
С сегодняшними процессорами и архитектурами кеша в сочетании с интеллектуальными компиляторами C ++ нам следует перестать беспокоиться о накладных расходах виртуальной диспетчеризации или RTTI. На моем ПК я могу протестировать вектор из 50 000 000 объектов с помощью dynamic_cast<> менее чем за секунду, и это включает в себя оператор if и другую инструкцию для каждого пути ветвления.
// populate vector
std::vector<A*> avec;
for (int i = 0; i < 50000000; i++) {
if (i % 2)
avec.push_back(new A());
else
avec.push_back(new B());
}
clock_t begin = clock();
int j = 0;
for (int i = 0; i < 50000000; i++) {
if (dynamic_cast<B*>(avec[i])) {
j += 1;
}
else
j -= 1;
}
clock_t end = clock();
double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
cout << "dynamic_cast: " << elapsed_secs << std::endl;
cout << "j=" << j << std::endl;
PS: если вы попробуете этот код, я рекомендую использовать меньший вектор и просто повторять несколько раз. Я на самом деле скомпилировал и запустил как проект x64, но суть в том, чтобы НЕ выделять 50-миллионный вектор элементов, а в том, чтобы показать виртуальную диспетчеризацию через несколько случайных указателей на объекты. Для 32-битной системы, скорее всего, возникнет ошибка нехватки памяти.
Если виртуальная диспетчеризация C ++ слишком медленная для ваших целей, то вы, вероятно, будете меньше заботиться об элегантном объектно-ориентированном дизайне, а больше о настройке каждого последнего цикла. В этом случае отбросьте виртуальные функции, широко используйте встроенные функции или напишите не на объектно-ориентированном языке Си или ассемблере.