class Base {
// ...
virtual void Foo(int x = 0);
};
class Derived : public Base {
// ...
virtual void Foo(int x = 1); // лучше так не делать...
};
Derived *pD = new Derived;
pD->Foo(); // Вызов pD->Foo(1)
Base *pB = pD;
pB->Foo(); // вызов pB->Foo(0)
У некоторых может вызвать удивление, что одна и та же функция-член одного и того же объекта получает разные аргументы в зависимости от статического типа, посредством которого к ней выполняется обращение.
Желательно добавлять ключевое слово virtual
при перекрытии функций, несмотря на его избыточность — это сделает код более удобным для чтения и понимания.
Не забывайте о том, что перекрытие может скрывать перегруженные функции из базового класса, например:
class Base{ // ...
virtual void Foo(int);
virtual void Foo(int, int);
void Foo(int, int, int);
};
class Derived : public Base { // ...
virtual void Foo(int); // Перекрывает Base::Foo(int),
// скрывая остальные функции
};
Derived d;
d.Foo(1); // Все в порядке
d.Foo(1, 2); // Ошибка
d.Foo(1, 2, 3); // Ошибка
Если перегруженные функции из базового класса должны быть видимы, воспользуйтесь объявлением using
для того, чтобы повторно объявить их в производном классе:
class Derived : public Base { // ...
virtual void Foo(int); // Перекрытие Base::Foo(int)
using Base::Foo; // вносит все прочие перегрузки
// Base::Foo в область видимости
};
Bird
(Птица) определяет виртуальную функцию Fly
и вы порождаете новый класс Ostrich
(известный как птица, которая не летает) из класса Bird
, то как вы реализуете Ostrich::Fly
? Ответ стандартный — 'по обстоятельствам'. Если Bird::Fly
гарантирует успешность (т.е. обеспечивает гарантию бессбойности; см. рекомендацию 71), поскольку способность летать есть неотъемлемой частью модели Bird
, то класс Ostrich
оказывается неадекватной реализацией такой модели.
39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными
В базовых классах с высокой стоимостью изменений (в частности, в библиотеках) лучше делать открытые функции невиртуальными. Виртуальные функции лучше делать закрытыми, или защищенными — если производный класс должен иметь возможность вызывать их базовые версии (этот совет не относится к деструкторам; см. рекомендацию 50).
Большинство из нас на собственном горьком опыте выучило правило, что члены класса должны быть закрытыми, если только мы не хотим специально обеспечить доступ к ним со стороны внешнего кода. Это просто правило хорошего тона обычной инкапсуляции. В основном это правило применимо к членам- данным (см. рекомендацию 41), но его можно с тем же успехом использовать для любых членов класса, включая виртуальные функции.
В частности, в объектно-ориентированных иерархиях, внесение изменений в которые обходится достаточно дорого, предпочтительна полная абстракция: лучше делать открытые функции невиртуальными, а виртуальные функции — закрытыми (или защищенными, если производные классы должны иметь возможность вызывать базовые версии). Это — шаблон проектирования Nonvirtual Interface (NVI). (Этот шаблон похож на другие, в особенности на Template Method [Gamma95], но имеет другую мотивацию и предназначение.)
Открытая виртуальная функция по своей природе решает две различные параллельные задачи.
•
•
В связи с существенным различием целей этих двух задач, они могут конфликтовать друг с другом (и зачастую так и происходит), так что одна функция не в состоянии в полной мере решить одновременно две задачи. То, что перед открытой виртуальной функцией ставятся две существенно различные задачи, является признаком недостаточно хорошего разделения зон ответственности и по сути нарушения рекомендаций 5 и 11, так что нам следует рассмотреть иной подход к проектированию.
Путем разделения открытых функций от виртуальных мы достигаем следующих значительных преимуществ.
• Process
, которая выполняет логическую единицу работы, в то время как разработчик данного класса может предпочесть перекрыть только некоторые части этой работы, что естественным образом моделируется путем независимо перекрываемых виртуальных функций (например, DoProcessPhase1
, DoProcessPhase2
), так что производному классу нет