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

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

Шрифт:

Начинающие программисты зачастую выполняют наследование от классов-значений, таких как класс

string
(стандартный или иной) просто чтобы "добавить больше функциональности". Однако определение свободной функции (не являющейся членом) существенно превосходит создание класса
super_string
по следующим причинам.

• Свободные функции хорошо вписываются в существующий код, который работает с объектами

string
. Если же вместо этого вы предоставляете класс
super_string
, вам придется вносить изменения в ваш код, заменяя типы и сигнатуры функций.

• Функции интерфейса, которые получают параметры типа

string
, при использовании наследования должны сделать одно из трех: а) отказаться от дополнительной функциональности
super_string
(бесполезно), б) копировать свои аргументы в объекты
super_string
(расточительно) или в) преобразовать ссылки на
string
в ссылки на
super_string
(затруднительно и потенциально некорректно).

• Функции-члены

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

• Если класс

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

Словом, лучше добавлять новую функциональность посредством новых свободных (не являющихся членами) функций (см. рекомендацию 44). Чтобы избежать проблем поиска имен, убедитесь, что вы поместили функции в то же пространство имен, что и тип, для расширения функциональности которого они предназначены (см. рекомендацию 57). Некоторые программисты не любят свободные функции из-за их синтаксиса

Fun(str)
вместо
str.Fun
, но это не более чем вопрос привычки.

Но что если класс

super_string
наследуется из класса
string
для добавления состояний, таких как кодировка или кэшированное значение количества слов? Открытое наследование не рекомендуется и в этом случае, поскольку класс
string
не защищен от срезки (см. рекомендацию 54), и любое копирование
super_string
в
string
молча уберет все старательно хранимые дополнительные состояния.

И наконец, наследование класса с открытым невиртуальным деструктором рискует получить эффект неопределенного поведения при удалении указателя на объект типа

string
, который на самом деле указывает на объект типа
super_string
(см. рекомендацию 50). Это неопределенное поведение может даже оказаться вполне допустимым при использовании вашего компилятора и распределителя памяти, но оно все равно рано или поздно выявится в виде затаившихся ошибок, утечек памяти, разрушенной кучи и кошмаров переноса на другую платформу.

Примеры

Пример 1. Композиция вместо открытого или закрытого наследования. Что делать, если вам нужен тип

lосаlized_string
, который "почти такой же, как и
string
, но с дополнительными данными и функциями и небольшими переделками имеющихся функций-членов
string
", и при этом реализация многих функций остается неизменной? В этом случае реализуйте ее с помощью класса
string
, но не наследованием, а комбинированием (что предупредит срезку и неопределенное полиморфное удаление), и добавьте транзитные функции для того, чтобы сделать видимыми функции класса string, оставшиеся неизменными:

class localized_string {

public:

 // ... Обеспечьте транзитные функции для тех

 // функций-членов string, которые остаются неизменными

 // (например, определите функцию insert, которая

 // вызывает impl_.insert) ...

 void clear; // Маскирует/переопределяет clear

 bool is_in_klingon const; // добавляет функциональность

private:

 std::string impl_;

 // ... дополнительные данные-члены ...

};

Конечно, писать транзитные функции для всех функций-членов, которые вы хотите сохранить, — занятие утомительное, но зато такая реализация существенно лучше и безопаснее, чем использование открытого или закрытого наследования.

Пример 2.

std::unary_function
. Хотя класс
std::unary_function
не имеет виртуальных функций, на самом деле он создан для использования в качестве базового класса и не противоречит рассматриваемой рекомендации. (Однако класс
unary_function
может быть усовершенствован добавлением защищенного деструктора — см. рекомендацию 50.)

Ссылки

[Dewhurst03] §70, §93 • [Meyers97] §33 • [Stroustrup00] §24.2-3, §25.2

36. Предпочитайте предоставление абстрактных интерфейсов

Резюме

Вы любите абстракционизм? Абстрактные интерфейсы помогают вам сосредоточиться на проблемах правильного абстрагирования, не вдаваясь в детали реализации или управления состояниями. Предпочтительно проектировать иерархии, реализующие абстрактные интерфейсы, которые моделируют абстрактные концепции.

  • Читать дальше
  • 1
  • ...
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • ...

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

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

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

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