Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация
- Название:Стандарты программирования на С++. 101 правило и рекомендация
- Автор:
- Жанр:
- Издательство:Издательский дом Вильямс
- Год:2005
- Город:Москва
- ISBN:5-8459-0859-0
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация краткое содержание
Эта книга поможет новичку стать профессионалом, так как в ней представлен сконцентрированный лучший опыт программистов на С++, обобщенный двумя экспертами мирового класса.
Начинающий программист найдет в ней простые и понятные рекомендации для ежедневного использования, подкрепленные примерами их конкретного применения на практике.
Опытные программисты найдут в ней советы и новые рекомендации, которые можно сразу же принять на вооружение. Программисты-профессионалы могут использовать эту книгу как основу для разработки собственных стандартов кодирования, как для себя лично, так и для группы, которой они руководят.
Конечно, книга рассчитана в первую очередь на профессиональных программистов с глубокими знаниями языка, однако она будет полезна любому, кто захочет углубить свои знания в данной области.
Стандарты программирования на С++. 101 правило и рекомендация - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
• По умолчанию используйте explicit в конструкторах с одним аргументом (см. рекомендацию 54):
class Widget { // ...
explicit Widget(unsigned int widgetizationFactor);
explicit Widget(const char* name, const Widget* other = 0);
};
• Используйте для преобразований типов именованные функции, а не соответствующие операторы:
class String { // ...
const char* as_char_pointer() const; // в традициях c_str
};
См. также обсуждение копирующих конструкторов, объявленных как explicit
, в рекомендации 54.
Пример 1. Перегрузка. Пусть у нас есть, например, Widget::Widget(unsigned int)
, который может быть вызван неявно, и функция Display
, перегруженная для Widget
и double
. Рассмотрим следующий сюрприз при разрешении перегрузки:
void Display(double); // вывод double
void Display(const Widget&); // Вывод Widget
Display(5); // гм! Создание и вывод Widget
Пример 2. Работающие ошибки. Допустим, вы снабдили класс String
оператором operator const char*
:
class String {
// ...
public:
operator const char*(); // Грустное решение...
};
В результате этого становятся компилируемыми масса глупостей и опечаток. Пусть s1
и s2
— объекты типа String
. Все приведенные ниже строки компилируются:
int x = s1 - s2; // Неопределенное поведение
const char* р = s1 - 5; // Неопределенное поведение
р = s1 + '0'; // делает не то, что вы ожидаете
if (s1 == "0") { ... } // делает не то, что вы ожидаете
Именно по этой причине в стандартном классе string
отсутствует operator const char*
.
При нечастом и осторожном использовании неявные преобразования типов могут сделать код более коротким и интуитивно более понятным. Стандартный класс std::string
определяет неявный конструктор, который получает один аргумент типа const char*
. Такое решение отлично работает, поскольку проектировщики класса приняли определенные меры предосторожности.
• Не имеется автоматического преобразования std::string
в const char*
; такое преобразование типов выполняются при помощи двух именованных функций — c_str
и data
.
• Все операторы сравнений, определенные для std::string
(например, ==
, !=
, <
), перегружены для сравнения const char*
и std::string
в любом порядке (см. рекомендацию 29). Это позволяет избежать создания скрытых временных переменных.
Но и при этом возникают определенные неприятности, связанные с перегрузкой функций.
void Display(int);
void Display(std::string);
Display(NULL); // вызов Display(int)
Этот результат для некоторых может оказаться сюрпризом. (Кстати, если бы выполнялся вызов Display(std::string)
, код бы обладал неопределенным поведением, поскольку создание std::string
из нулевого указателя некорректно, но конструктор этого класса не обязан проверять передаваемое ему значение на равенство нулю.)
[Dewhurst03] §36-37 • [Lakos96] §9.3.1 • [Meyers96] §5 • [Murray93] §2.4 • [Sutter00] §6, §20, §39
41. Делайте данные-члены закрытыми (кроме случая агрегатов в стиле структур С)
Данные-члены должны быть закрыты. Только в случае простейших типов в стиле структур языка С, объединяющих в единое целое набор значений, не претендующих на инкапсуляцию и не обеспечивающих поведение, делайте все данные-члены открытыми. Избегайте смешивания открытых и закрытых данных, что практически всегда говорит о бестолковом дизайне.
Сокрытие информации является ключом к качественной разработке программного обеспечения (см. рекомендацию 11). Желательно делать все данные-члены закрытыми; закрытые данные — лучшее средство для сохранения инварианта класса, в том числе при возможных вносимых изменениях.
Открытые данные — плохая идея, если класс моделирует некоторую абстракцию и, следовательно, должен поддерживать инварианты. Наличие открытых данных означает, что часть состояния вашего класса может изменяться неконтролируемо, непредсказуемо и асинхронно с остальной частью состояния. Это означает, что абстракция разделяет ответственность за поддержание одного или нескольких инвариантов с неограниченным множеством кода, который использует эту абстракцию, и совершенно очевидно, что такое положение дел просто недопустимо с точки зрения корректного проектирования.
Защищенные данные обладают всеми недостатками открытых данных, поскольку наличие защищенных данных означает, что абстракция разделяет ответственность за поддержание одного или нескольких инвариантов с неограниченным множеством кода — теперь это код существующих и будущих производных классов. Более того, любой код может читать и модифицировать защищенные данные так же легко, как и открытые — просто создав производный класс и используя его для доступа к данным.
Смешивание открытых и закрытых данных-членов в одном и том же классе является непоследовательным и попросту запутывает пользователей. Закрытые данные демонстрируют, что у вас есть некоторые инварианты и нечто, предназначенное для их поддержания. Смешивание их с открытыми данными-членами означает, что при проектировании так окончательно и не решено, должен ли класс представлять некоторую абстракцию или нет.
Не закрытые данные-члены почти всегда хуже даже простейших функций для получения и установки значений, поскольку последние обеспечивают устойчивость кода к возможным внесениям изменений.
Подумайте о сокрытии закрытых членов класса с использованием идиомы Pimpl (см. рекомендацию 43).
Пример 1. Корректная инкапсуляция. Большинство классов (например, Matrix
, File
, Date
, BankAccount
, Security
) должны закрывать все данные-члены и открывать соответствующие интерфейсы. Позволение вызывающему коду непосредственно работать с внутренними данными класса работает против представленной им абстракции и поддерживаемых им инвариантов.
Агрегат Node
, широко используемый в реализации класса List
, обычно содержит некоторые данные и два указателя на Node
: next_
и prev_
. Данные-члены Node
не должны быть скрыты от List
. Однако не забудьте рассмотреть еще пример 3.
Пример 2. TreeNode. Рассмотрим контейнер Tree
, реализованный с использованием TreeNode
, агрегата, используемого в Tree
, который хранит указатели на предыдущий, следующий и родительский узлы и сам объект T
. Все члены TreeNode
могут быть открытыми, поскольку их не надо скрывать от класса Tree
, который непосредственно манипулирует ими. Однако класс Tree
должен полностью скрывать класс TreeNode
(например, как вложенный закрытый класс или как определенный только в файле реализации класса Tree
), поскольку это — детали внутренне реализации класса Tree
, от которых не должен зависеть и с которыми не должен иметь дела вызывающий код. И наконец, Tree
не скрывает содержащиеся в контейнере объекты T
, поскольку за них отвечает вызывающий код; контейнеры используют абстракцию итераторов для предоставления доступа к содержащимся объектам, в то время как внутренняя структура контейнера остается скрытой.
Интервал:
Закладка: