Чтение онлайн

на главную - закладки

Жанры

Стандарты программирования на С++. 101 правило и рекомендация

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

Шрифт:
Исключения

Классы стратегий добавляют новое поведение путем открытого наследования, но это не является неверным употреблением открытого наследования для моделирования отношения "реализован посредством".

Ссылки

[Cargill92] pp. 19-20 • [Cline99] §5.13, §7.01-8.15 • [Dewhurst03] §92 • [Liskov88] • [Meyers97] §35 • [Stroustrup00] §12.4.1, §23.4.3.1, §24.3.4 • [Sutter00] §22-24

38. Практикуйте безопасное перекрытие

Резюме

Ответственно подходите в перекрытию функций. Когда вы перекрываете

виртуальную функцию, сохраняйте заменимость; в частности, обратите внимание на пред- и постусловия в базовом классе. Не изменяйте аргументы по умолчанию виртуальных функций. Предпочтительно явно указывать перекрываемые функции как виртуальные. Не забывайте о сокрытии перегруженных функций в базовом классе.

Обсуждение

Хотя обычно производные классы добавляют новые состояния (т.е. члены-данные), они моделируют подмножества (а не надмножества) своих базовых классов. При корректном наследовании производный класс моделирует частный случай более общей базовой концепции (см. рекомендацию 37).

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

Определение в производном классе перекрытия, которое может быть неуспешным (например, генерировать исключения; см. рекомендацию 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).

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

Черный маг императора 3

Герда Александр
3. Черный маг императора
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Черный маг императора 3

Повелитель механического легиона. Том VIII

Лисицин Евгений
8. Повелитель механического легиона
Фантастика:
технофэнтези
аниме
фэнтези
5.00
рейтинг книги
Повелитель механического легиона. Том VIII

Пипец Котенку! 3

Майерс Александр
3. РОС: Пипец Котенку!
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Пипец Котенку! 3

Разбуди меня

Рам Янка
7. Серьёзные мальчики в форме
Любовные романы:
современные любовные романы
остросюжетные любовные романы
5.00
рейтинг книги
Разбуди меня

Боги, пиво и дурак. Том 6

Горина Юлия Николаевна
6. Боги, пиво и дурак
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Боги, пиво и дурак. Том 6

Болотник 2

Панченко Андрей Алексеевич
2. Болотник
Фантастика:
попаданцы
альтернативная история
6.25
рейтинг книги
Болотник 2

Ты всё ещё моя

Тодорова Елена
4. Под запретом
Любовные романы:
современные любовные романы
7.00
рейтинг книги
Ты всё ещё моя

S-T-I-K-S. Пройти через туман

Елисеев Алексей Станиславович
Вселенная S-T-I-K-S
Фантастика:
боевая фантастика
7.00
рейтинг книги
S-T-I-K-S. Пройти через туман

Имя нам Легион. Том 4

Дорничев Дмитрий
4. Меж двух миров
Фантастика:
боевая фантастика
рпг
аниме
5.00
рейтинг книги
Имя нам Легион. Том 4

Сводный гад

Рам Янка
2. Самбисты
Любовные романы:
современные любовные романы
эро литература
5.00
рейтинг книги
Сводный гад

Я князь. Книга XVIII

Дрейк Сириус
18. Дорогой барон!
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Я князь. Книга XVIII

Королевская Академия Магии. Неестественный Отбор

Самсонова Наталья
Любовные романы:
любовно-фантастические романы
8.22
рейтинг книги
Королевская Академия Магии. Неестественный Отбор

Последняя Арена 6

Греков Сергей
6. Последняя Арена
Фантастика:
рпг
постапокалипсис
5.00
рейтинг книги
Последняя Арена 6

Жребий некроманта. Надежда рода

Решетов Евгений Валерьевич
1. Жребий некроманта
Фантастика:
фэнтези
попаданцы
6.50
рейтинг книги
Жребий некроманта. Надежда рода