- •Герб Саттер
- •Как пользоваться этой книгой
- •Стандарты кодирования и вы
- •Об этой книге
- •Благодарности
- •Вопросы организации и стратегии
- •0. Не мелочитесь, или Что не следует стандартизировать Резюме
- •Обсуждение
- •Примеры
- •1. Компилируйте без замечаний при максимальном уровне предупреждений Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •2. Используйте автоматические системы сборки программ Резюме
- •Обсуждение
- •3. Используйте систему контроля версий Резюме
- •Обсуждение
- •Исключения
- •Стиль проектирования
- •5. Один объект — одна задача Резюме
- •Обсуждение
- •Примеры
- •6. Главное — корректность, простота и ясность Резюме
- •Обсуждение
- •Примеры
- •7. Кодирование с учетом масштабируемости Резюме
- •Обсуждение
- •8. Не оптимизируйте преждевременно Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •10. Минимизируйте глобальные и совместно используемые данные Резюме
- •Обсуждение
- •Исключения
- •11. Сокрытие информации Резюме
- •Обсуждение
- •Исключения
- •13. Ресурсы должны быть во владении объектов Резюме
- •Обсуждение
- •Исключения
- •Стиль кодирования
- •14. Предпочитайте ошибки компиляции и компоновки ошибкам времени выполнения Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •Примеры
- •16. Избегайте макросов Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •17. Избегайте магических чисел Резюме
- •Обсуждение
- •Примеры
- •18. Объявляйте переменные как можно локальнее Резюме
- •Обсуждение
- •Исключения
- •Примеры
- •Исключения
- •Исключения
- •22. Минимизируйте зависимости определений и избегайте циклических зависимостей Резюме
- •Обсуждение
- •Исключения
- •Примеры
- •24. Используйте только внутреннюю, но не внешнюю защиту директивы #include Резюме
- •Обсуждение
- •Исключения
- •Функции и операторы
- •25. Передача параметров по значению, (интеллектуальному) указателю или ссылке Резюме
- •Обсуждение
- •26. Сохраняйте естественную семантику перегруженных операторов Резюме
- •Обсуждение
- •Исключения
- •27. Отдавайте предпочтение каноническим формам арифметических операторов и операторов присваивания Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •30. Избегайте перегрузки &&, || и , (запятой) Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •Проектирование классов и наследование
- •32. Ясно представляйте, какой вид класса вы создаете Резюме
- •Обсуждение
- •33. Предпочитайте минимальные классы монолитным Резюме
- •Обсуждение
- •34. Предпочитайте композицию наследованию Резюме
- •Обсуждение
- •Исключения
- •35. Избегайте наследования от классов, которые не спроектированы для этой цели Резюме
- •Обсуждение
- •36. Предпочитайте предоставление абстрактных интерфейсов Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •Исключения
- •Примеры
- •39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными Резюме
- •Обсуждение
- •Исключения
- •Примеры
- •Исключения
- •41. Делайте данные-члены закрытыми (кроме случая агрегатов в стиле структур с) Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •42. Не допускайте вмешательства во внутренние дела Резюме
- •Обсуждение
- •Исключения
- •43. Разумно пользуйтесь идиомой Pimpl Резюме
- •Обсуждение
- •Исключения
- •Примеры
- •Исключения
- •Конструкторы, деструкторы и копирование
- •47. Определяйте и инициализируйте переменные-члены в одном порядке Резюме
- •Обсуждение
- •48. В конструкторах предпочитайте инициализацию присваиванию Резюме
- •Обсуждение
- •Исключения
- •Примеры
- •50. Делайте деструкторы базовых классов открытыми и виртуальными либо защищенными и невиртуальными Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •51. Деструкторы, функции освобождения ресурсов и обмена не ошибаются Резюме
- •Обсуждение
- •52. Копируйте и ликвидируйте согласованно Резюме
- •Обсуждение
- •Исключения
- •53. Явно разрешайте или запрещайте копирование Резюме
- •Обсуждение
- •54. Избегайте срезки. Подумайте об использовании в базовом классе клонирования вместо копирования Резюме
- •Обсуждение
- •Исключения
- •56. Обеспечьте бессбойную функцию обмена Резюме
- •Обсуждение
- •Исключения
- •Пространства имен и модули
- •57. Храните типы и их свободный интерфейс в одном пространстве имен Резюме
- •Обсуждение
- •Примеры
- •58. Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы Резюме
- •Обсуждение
- •59. Не используйте using для пространств имен в заголовочных файлах или перед директивой #include Резюме
- •Обсуждение
- •Исключения
- •60. Избегайте выделения и освобождения памяти в разных модулях Резюме
- •Обсуждение
- •61. Не определяйте в заголовочном файле объекты со связыванием Резюме
- •Обсуждение
- •Исключения
- •62. Не позволяйте исключениям пересекать границы модулей Резюме
- •Обсуждение
- •63. Используйте достаточно переносимые типы в интерфейсах модулей Резюме
- •Обсуждение
- •Примеры
- •65. Выполняйте настройку явно и преднамеренно Резюме
- •Обсуждение
- •66. Не специализируйте шаблоны функций Резюме
- •Обсуждение
- •67. Пишите максимально обобщенный код Резюме
- •Обсуждение
- •Исключения
- •Обработка ошибок и исключения
- •68. Широко применяйте assert для документирования внутренних допущений и инвариантов Резюме
- •Обсуждение
- •Примеры
- •69. Определите разумную стратегию обработки ошибок и строго ей следуйте Резюме
- •Обсуждение
- •70. Отличайте ошибки от ситуаций, не являющихся ошибками Резюме
- •Обсуждение
- •Примеры
- •71. Проектируйте и пишите безопасный в отношении ошибок код Резюме
- •Обсуждение
- •Примеры
- •72. Для уведомления об ошибках следует использовать исключения Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •73. Генерируйте исключения по значению, перехватывайте — по ссылке Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •Исключения
- •Stl: контейнеры
- •76. По умолчанию используйте vector. В противном случае выбирайте контейнер, соответствующий задаче Резюме
- •Обсуждение
- •Примеры
- •77. Вместо массивов используйте vector и string Резюме
- •Обсуждение
- •78. Используйте vector (и string::c_str) для обмена данными с api на других языках Резюме
- •Обсуждение
- •79. Храните в контейнерах только значения или интеллектуальные указатели Резюме
- •Обсуждение
- •Примеры
- •80. Предпочитайте push_back другим способам расширения последовательности Резюме
- •Обсуждение
- •Исключения
- •81. Предпочитайте операции с диапазонами операциям с отдельными элементами Резюме
- •Обсуждение
- •Примеры
- •82. Используйте подходящие идиомы для реального уменьшения емкости контейнера и удаления элементов Резюме
- •Обсуждение
- •Исключения
- •Stl: алгоритмы
- •83. Используйте отладочную реализацию stl Резюме
- •Обсуждение
- •Примеры
- •84. Предпочитайте вызовы алгоритмов самостоятельно разрабатываемым циклам Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •85. Пользуйтесь правильным алгоритмом поиска Резюме
- •Обсуждение
- •86. Пользуйтесь правильным алгоритмом сортировки Резюме
- •Обсуждение
- •Примеры
- •Исключения
- •87. Делайте предикаты чистыми функциями Резюме
- •Обсуждение
- •Примеры
- •88. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции Резюме
- •Обсуждение
- •89. Корректно пишите функциональные объекты Резюме
- •Обсуждение
- •Безопасность типов
- •90. Избегайте явного выбора типов — используйте полиморфизм Резюме
- •Обсуждение
- •Примеры
- •91. Работайте с типами, а не с представлениями Резюме
- •Обсуждение
- •92. Избегайте reinterpret_cast Резюме
- •Обсуждение
- •Исключения
- •93. Избегайте применения static_cast к указателям Резюме
- •Обсуждение
- •94. Избегайте преобразований, отменяющих const Резюме
- •Обсуждение
- •Исключения
- •95. Не используйте преобразование типов в стиле с Резюме
- •Обсуждение
- •96. Не применяйте memcpy или memcmp к не-pod типам Резюме
- •Обсуждение
- •97. Не используйте объединения для преобразований Резюме
- •Обсуждение
- •Исключения
- •99. Не используйте недействительные объекты и небезопасные функции Резюме
- •Обсуждение
- •100. Не рассматривайте массивы полиморфно Резюме
- •Обсуждение
- •Список литературы
- •Резюме из резюме
- •8. Не оптимизируйте преждевременно
- •15. Активно используйте const
- •23. Делайте заголовочные файлы самодостаточными
- •36. Предпочитайте предоставление абстрактных интерфейсов
- •61. Не определяйте в заголовочном файле объекты со связыванием
- •62. Не позволяйте исключениям пересекать границы модулей
- •63. Используйте достаточно переносимые типы в интерфейсах модулей
- •70. Отличайте ошибки от ситуаций, не являющихся ошибками
- •71. Проектируйте и пишите безопасный в отношении ошибок код
- •72. Для уведомления об ошибках следует использовать исключения
- •73. Генерируйте исключения по значению, перехватывайте — по ссылке
- •85. Пользуйтесь правильным алгоритмом поиска
- •86. Пользуйтесь правильным алгоритмом сортировки
- •87. Делайте предикаты чистыми функциями
- •88. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
48. В конструкторах предпочитайте инициализацию присваиванию Резюме
В конструкторах использование инициализации вместо присваивания для установки значений переменных-членов предохраняет от ненужной работы времени выполнения при том же объеме вводимого исходного текста.
Обсуждение
Конструкторы генерируют скрытый код инициализации. Рассмотрим следующий код:
class A {
string s1_, s2_;
public:
A() { s1_ = "Hello, "; s2_ = "world"; }
};
В действительности сгенерированный код конструктора выглядит так, как если бы вы написали:
А() : s1_(), s2_() { s1_ = "Hello, "; s2_ = "world"; }
To есть объекты, не инициализированные вами явно, автоматически инициализируются с использованием их конструкторов по умолчанию, после чего выполняется присваивание значений с использованием их операторов присваивания. Чаще всего операторы присваивания нетривиальных объектов выполняют немного больше работы, чем конструкторы, поскольку работают с уже созданными объектами.
Таким образом, инициализация переменных-членов в списке инициализации дает код, лучше выражающий ваши намерения и обычно более быстрый и меньшего размера:
А() : s1_("Hello, "), s2_("world ") { }
Эта методика не является преждевременной оптимизацией; это — избежание преждевременной пессимизации (см. рекомендацию 9).
Исключения
Всегда выполняйте захват неуправляемого ресурса (например, выделение памяти оператором new, результат которого не передается немедленно конструктору интеллектуального указателя) в теле конструктора, а не в списке инициализации (см. [Sutter02]). Конечно, лучше всего вообще не использовать таких небезопасных и не имеющих владельца ресурсов (см. рекомендацию 13).
Ссылки
[Dewhurst03] §51, §59 • [Keffer95] pp.13-14 • [Meyers97] §12 • [Murray93] §2.1.31 • [Sutter00] §8, §47 • [Sutter02] §18
49. Избегайте вызовов виртуальных функций в конструкторах и деструкторах
Резюме
Внутри конструкторов и деструкторов виртуальные функции теряют виртуальность. Хуже того — все прямые или косвенные вызовы нереализованных чисто виртуальных функций из конструктора или деструктора приводят к неопределенному поведению. Если ваш дизайн требует виртуальной передачи в производный класс из конструктора или деструктора базового класса, следует воспользоваться иной методикой, например, постконструкторами.
Обсуждение
В C++ полный объект конструируется по одному базовому классу за раз.
Пусть у нас есть базовый класс В и класс D, производный от B. При создании объекта D, когда выполняется конструктор В, динамическим типом создаваемого объекта является B. В частности, вызов виртуальной функции B::Fun приведет к выполнению функции Fun, определенной в классе В, независимо от того, перекрывает ее класс D или нет. И это хорошо, поскольку вызов функции-члена D в тот момент, когда члены объекта D еще не инициализированы, может привести к хаосу. Только после завершения выполнения конструктора В выполняется тело конструктора D и объект приобретает тип D. В качестве эмпирического правила следует помнить, что в процессе конструирования В нет никакого способа определить, является ли В отдельным объектом или базовой частью некоторого иного производного объекта.
Кроме того, следует помнить, что вызов из конструктора чисто виртуальной функции, не имеющей определения, приводит к неопределенному поведению.
С другой стороны, в некоторых случаях дизайн требует использования "постконструктора", т.е. виртуальной функции, которая должна быть вызвана после того, как полный объект оказывается сконструирован. Некоторые методики, применяемые для решения этой задачи, описаны в приводимых ниже ссылках. Вот (далеко не исчерпывающий) список возможных решений.
• Перекладывание ответственности. Можно просто документировать необходимость вызова в пользовательском коде постконструктора после создания объекта.
• Отложенная постинициализация. Можно выполнять постинициализацию при первом вызове функции-члена, для чего использовать в базовом классе флаг типа bool, который показывает, был уже вызван постконструктор или нет.
• Использование семантики базового класса. Правила языка требуют, чтобы конструктор наиболее производного класса определял, какой из конструкторов базовых классов будет вызван. Вы можете использовать это правило в своих целях (см. [Taligent94]).
• Использование функции-фабрики. Таким способом вы можете легко обеспечить принудительный вызов функции-постконструктора (см. примеры к данной рекомендации).
Ни одна из методик постконструирования не является идеальной. Наихудшее, что можно сделать, — это потребовать, чтобы пользователь класса вручную вызывал постконструктор. Однако даже наилучшие способы решения данной задачи требуют отличного от обычного синтаксиса конструирования объекта (что легко проверяется в процессе компиляции) и/или сотрудничества с авторами производных классов (что невозможно проверить в процессе компиляции).