- •Герб Саттер
 - •Как пользоваться этой книгой
 - •Стандарты кодирования и вы
 - •Об этой книге
 - •Благодарности
 - •Вопросы организации и стратегии
 - •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. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
 
Обсуждение
Главная причина отказа от перегрузки операторов operator&&, operator|| и operator, (запятая) состоит в том, что вы не имеете возможности реализовать полную семантику встроенных операторов в этих трех случаях, а программисты обычно ожидают ее выполнения. В частности, встроенные версии выполняют вычисление слева направо, а для операторов && и || используются сокращенные вычисления.
Встроенные версии && и || сначала вычисляют левую часть выражения, и если она полностью определяет результат (false для &&, true для ||), то вычислять правое выражение незачем — и оно гарантированно не будет вычисляться. Таким образом мы используем эту возможность, позволяя корректности правого выражения зависеть от успешного вычисления левого:
Employee* е = TryToGetEmployee();
if (е && e->Manager())
// ...
Корректность этого кода обусловлена тем, что e->Manager() не будет вычисляться, если e имеет нулевое значение. Это совершенно обычно и корректно — до тех пор, пока не используется перегруженный оператор operator&&, поскольку в таком случае выражение, включающее &&, будет следовать правилам функции:
• вызовы функций всегда вычисляют все аргументы до выполнения кода функции;
• порядок вычисления аргументов функций не определен (см. также рекомендацию 31). Давайте рассмотрим модернизированную версию приведенного ранее фрагмента, которая
использует интеллектуальные указатели:
some_smart_ptr<Employee> е = TryToGetEmployee();
if (е && e->Manager())
// ...
Пусть в этом коде используется перегруженный оператор operator&& (предоставленный автором some_smart_ptr или Employee). Тогда мы получаем код, который для читателя выглядит совершенно корректно, но потенциально может вызвать e->Manager() при нулевом значении e.
Некоторый иной код может не привести к аварийному завершению программы, но стать некорректным по другой причине — из-за зависимости от порядка вычислений двух выражений. Результат может оказаться плачевным. Например:
if (DisplayPrompt() && GetLine()) // ...
Если оператор operator&& переопределен пользователем, то неизвестно, какая из функций — DisplayPrompt или GetLine — будет вызвана первой. Программа в результате может ожидать ввода пользователя до того, как будет выведено соответствующее поясняющее приглашение.
Конечно, такой код может заработать при использовании вашего конкретного компилятора и настроек сборки. Но это — очень ненадежно. Компиляторы могут выбрать любой порядок вычислений (и так они и поступают), который сочтут нужным для данного конкретного вызова, принимая во внимание такие факторы, как размер генерируемого кода, доступные регистры, сложность выражений и другие. Так что один и тот же вызов может проявлять себя по-разному в зависимости от версии компилятора, настроек компиляции и даже инструкций, окружающих данный вызов.
Та же ненадежность наблюдается и в случае оператора-запятой. Так же, как и операторы && и ||, встроенный оператор-запятая гарантирует, что выражения будут вычислены слева направо (в отличие от && и ||, здесь всегда вычисляются оба выражения). Пользовательский оператор-запятая не может гарантировать вычислений слева направо, что обычно приводит к удивительным результатам. Например, если в следующем коде используется пользовательский оператор-запятая, то неизвестно, получит ли функция g аргумент 0 или 1:
int i = 0;
f(i++), g(i); //См. также рекомендацию 31
