Вход/Регистрация
Стандарты программирования на С++. 101 правило и рекомендация
вернуться

Александреску Андрей

Шрифт:

Это имеет прямые следствия для корректного перекрытия функций. Соблюдение отношения включения влечет за собой заменимость — операции, которые применимы ко всему множеству, применимы и к любому его подмножеству. Если базовый класс гарантирует выполнение определенных пред- и постусловий некоторой операции, то и любой производный класс должен обеспечить их выполнение. Перекрытие может предъявлять меньше требований и предоставлять большую функциональность, но никогда не должно предъявлять большие требования или меньше обещать — поскольку это приведет к нарушению контракта с вызывающим кодом.

Определение в производном классе перекрытия, которое может быть неуспешным (например, генерировать исключения; см. рекомендацию 70), будет корректно только в том случае, когда в базовом классе не объявлено, что данная операция всегда успешна. Например, скажем, класс

Employee
содержит виртуальную функцию-член
GetBuilding
, предназначение которой — вернуть код здания, в котором работает объект
Employee
. Но что если мы захотим написать производный класс
RemoteContractor
, который перекрывает функцию
GetBuilding
, в результате чего она может генерировать исключения или возвращать нулевой код здания? Такое поведение корректно только в том случае, если в документации класса
Employee
указано, что функция
GetBuilding
может завершаться неуспешно, и в классе
RemoteContractor
сообщение о неудаче выполняется документированным в классе
Employee
способом.

Никогда не изменяйте аргумент по умолчанию при перекрытии. Он не является частью сигнатуры функции, и клиентский код будет невольно передавать различные аргументы в функцию, в зависимости от того, какой узел иерархии обращается к ней. Рассмотрим следующий пример:

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 в область видимости

};

Примеры

Пример:

Ostrich
(Страус). Если класс
Bird
(Птица) определяет виртуальную функцию
Fly
и вы порождаете новый класс
Ostrich
(известный как птица, которая не летает) из класса
Bird
, то как вы реализуете
Ostrich::Fly
? Ответ стандартный — "по обстоятельствам". Если
Bird::Fly
гарантирует успешность (т.е. обеспечивает гарантию бессбойности; см. рекомендацию 71), поскольку способность летать есть неотъемлемой частью модели
Bird
, то класс
Ostrich
оказывается неадекватной реализацией такой модели.

Ссылки

[Dewhurst03] §73-74, §78-79 • [Sutter00] §21 • [Keffer95] p. 18

39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными

Резюме

В базовых классах с высокой стоимостью изменений (в частности, в библиотеках) лучше делать открытые функции невиртуальными. Виртуальные функции лучше делать закрытыми, или защищенными — если производный класс должен иметь возможность вызывать их базовые версии (этот совет не относится к деструкторам; см. рекомендацию 50).

  • Читать дальше
  • 1
  • ...
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: