Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация
- Название:Стандарты программирования на С++. 101 правило и рекомендация
- Автор:
- Жанр:
- Издательство:Издательский дом Вильямс
- Год:2005
- Город:Москва
- ISBN:5-8459-0859-0
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация краткое содержание
Эта книга поможет новичку стать профессионалом, так как в ней представлен сконцентрированный лучший опыт программистов на С++, обобщенный двумя экспертами мирового класса.
Начинающий программист найдет в ней простые и понятные рекомендации для ежедневного использования, подкрепленные примерами их конкретного применения на практике.
Опытные программисты найдут в ней советы и новые рекомендации, которые можно сразу же принять на вооружение. Программисты-профессионалы могут использовать эту книгу как основу для разработки собственных стандартов кодирования, как для себя лично, так и для группы, которой они руководят.
Конечно, книга рассчитана в первую очередь на профессиональных программистов с глубокими знаниями языка, однако она будет полезна любому, кто захочет углубить свои знания в данной области.
Стандарты программирования на С++. 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. Предпочитайте предоставление абстрактных интерфейсов
Вы любите абстракционизм? Абстрактные интерфейсы помогают вам сосредоточиться на проблемах правильного абстрагирования, не вдаваясь в детали реализации или управления состояниями. Предпочтительно проектировать иерархии, реализующие абстрактные интерфейсы, которые моделируют абстрактные концепции.
Предпочитайте определять абстрактные интерфейсы и выполнять наследование от них. Абстрактный интерфейс представляет собой абстрактный класс, составленный полностью из (чисто) виртуальных функций и не обладающий состояниями (членами-данными), причем обычно без реализации функций-членов. Заметим, что отсутствие состояний в абстрактном интерфейсе упрощает дизайн всей иерархии (см. соответствующие примеры в [Meyers96]).
Желательно следовать принципу инверсии зависимостей (Dependency Inversion Principle, DIP; см. [Martin96a] и [Martin00]). Данный принцип состоит в следующем.
• Высокоуровневые модули не должны зависеть от низкоуровневых. И те, и другие должны зависеть от абстракций.
• Абстракции не должны зависеть от деталей; вместо этого детали должны зависеть от абстракций.
Из DIP следует, что корнями иерархий должны быть абстрактные классы, в то время как конкретные классы в этой роли выступать не должны (см. рекомендацию 35). Абстрактные базовые классы должны беспокоиться об определении функциональности, но не о ее реализации.
Принцип инверсии зависимостей имеет три фундаментальных преимущества при проектировании.
• Повышение надежности. Менее стабильные части системы (реализации) зависят от более стабильных частей (абстракций). Надежный дизайн — тот, в котором воздействие изменений ограничено. При плохом проектировании небольшие изменения в одном месте расходятся кругами по всему проекту и оказывают влияние на самые неожиданные части системы. Именно это происходит, когда проект строится на конкретных базовых классах.
Читать дальшеИнтервал:
Закладка: