Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
Шрифт:
Этот
Резюме
Из этого правила вы должны извлечь одну практическую рекомендацию: размышляя над тем, как решить стоящую перед вами задачу, имеет смысл рассматривать не только виртуальные функции. Вот краткий перечень предложенных альтернатив:
• Применение идиомы невиртуального интерфейса (NVI), варианта паттерна проектирования «Шаблонный Метод». Смысл ее в том, чтобы обернуть открытыми невиртуальными функциями-членами вызовы менее доступных виртуальных функций.
• Замена виртуальных функций членами данных – указателями на функции. Это упрощенное проявление паттерна проектирования «Стратегия».
• Замена виртуальных функций членами данных – tr1::function. Это позволяет применять любую вызываемую сущность, сигнатура которой совместима с той, что вам нужна. Это тоже форма паттерна проектирования «Стратегия».
• Замена виртуальных функций из одной иерархии виртуальными функциями из другой иерархии. Это традиционная реализация паттерна проектирования «Стратегия».
Это не исчерпывающий список альтернатив виртуальным функциям, но его должно хватить, чтобы убедить вас в том, что такие альтернативы существуют. Более того, из сравнения их достоинств и недостатков должны быть ясно, что рассматривать их стоит.
Чтобы не застрять в колее на дороге объектно-ориентированного проектирования, стоит время от времени резко поворачивать руль. Путей много. Потратьте время на знакомство с ними.
• К числу альтернатив виртуальным функциям относятся идиома NVI и различные формы паттерна проектирования «Стратегия». Идиома NVI сама по себе – это пример реализации паттерна «Шаблонный Метод».
• Недостаток переноса функциональности из функций-членов вовне класса заключается в том, что функциям-нечленам недостает прав доступа к закрытым членам класса.
• Объекты tr1::function работают как обобщенные указатели на функции. Такие объекты поддерживают все вызываемые сущности, совместимые с сигнатурой целевой функции.
Правило 36: Никогда не переопределяйте наследуемые невиртуальные функции
Предположим, я сообщаю вам, что класс D открыто наследует классу B и что в классе B определена открытая функция-член mf. Ее параметры и тип возвращаемого значения не важны, поэтому давайте просто предположим, что это void. Другими словами, я говорю следующее:
Даже ничего не зная о B, D или mf, имея объект x типа D,
вы, наверное, удивитесь, когда код
поведет себя иначе, чем
Ведь в обоих случаях вы вызываете функцию-член объекта x. Поскольку вы имеете дело с одной и той же функцией и одним и тем же объектом, поведение в обоих случаях должно быть одинаково, не так ли?
Да, так должно быть, но не всегда бывает. В частности, вы получите иной результат, если mf невиртуальна, а D определяет собственную версию mf:
Причина такого «двуличного» поведения заключается в том, что невиртуальные функции, подобные B::mf и D::mf, связываются статически (см. правило 37). Это означает, что когда pB объявляется как указатель на объект тип B, невиртуальные функции, вызываемые посредством pB, – это всегда функции, определения которых даны в классе B, даже если pB, как в данном примере, указывает на объект класса, производного от B.