Это практически такой же подход, как и прежде, за исключением того, что тело чисто виртуальной функции Airplane::fly заменяет собой независимую функцию Airplane::defaultFly. По существу, fly разбита на две основные составляющие. Объявление задает интерфейс (который
И наконец, пришла очередь невиртуальной функции класса Shape – objectID:
class Shape {
public:
int objectID() const;
...
};
Когда функция-член объявлена невиртуальной, не предполагается, что она будет вести себя иначе в производных классах. В действительности невиртуальные функции-члены выражают
• Цель объявления невиртуальной функции – заставить производные классы наследовать как ее
Вы можете представлять себе объявление Shape::objectID как утверждение: «Каждый объект Shape имеет функцию, которая дает идентификатор объекта, и этот идентификатор всегда вычисляется одним и тем же способом. Этот способ задается определением функции Shape::objectID, и никакой производный класс не должен его изменять». Поскольку невиртуальная функция определяет инвариант относительно специализации, ее не следует переопределять в производных классах (см. правило 36).
Разница в объявлениях чисто виртуальных, просто виртуальных и невиртуальных функций позволяет точно указать, что, по вашему замыслу, должны наследовать производные классы: только интерфейс, интерфейс и реализацию по умолчанию либо интерфейс и обязательную реализацию соответственно. Поскольку эти типы объявлений обозначают принципиально разные вещи, следует тщательно подходить к выбору подходящего варианта при объявлянии функции-члена. При этом вы должны избегать двух ошибок, чаще всего совершаемых неопытными проектировщиками классов.
Первая ошибка – объявление всех функций невиртуальными. Это не оставляет возможности для маневров в производных классах; при этом больше всего проблем вызывают невиртуальные деструкторы (см. правило 7). Конечно, нет ничего плохого в проектировании классов, которые не предполагается использовать в качестве базовых. В этом случае вполне уместен набор из одних только невиртуальных функций-членов. Однако очень часто такие классы объявляются либо из-за незнания различий между виртуальными и невиртуальными функциями, либо в результате необоснованного беспокойства по поводу потери производительности при использовании виртуальных функций. Факт остается фактом: практически любой класс, который должен использоваться как базовый, будет содержать виртуальные функции (см. правило 7).
Если вы обеспокоены тем, во что обходится использование виртуальных функций, позвольте мне напомнить вам эмпирическое правило «80–20» (см. также правило 30), которое утверждает, что в типичной программе 80 % времени исполнения затрачивается на 20 % кода. Это правило крайне важно, потому что оно означает, что в среднем 80 % ваших функций могут быть виртуальными, не оказывая ощутимого влияния на общую производительность программы. Прежде чем начать беспокоиться о том, можете ли вы позволить себе использование виртуальных функций, убедитесь, что вы имеете дело с теми 20 % программы, для которых ваше решение окажет существенное влияние на производительность.
Другая распространенная ошибка – объявление
• Наследование интерфейса отличается от наследования реализации. При открытом наследовании производные классы всегда наследуют интерфейсы базовых классов.
• Чисто виртуальные функции означают, что наследуется только интерфейс.
• Обычные виртуальные функции означают, что наследуются интерфейс и реализация по умолчанию.
• Невиртуальные функции означают, что наследуются интерфейс и обязательная реализация.