
- •Герб Саттер
- •Как пользоваться этой книгой
- •Стандарты кодирования и вы
- •Об этой книге
- •Благодарности
- •Вопросы организации и стратегии
- •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. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
63. Используйте достаточно переносимые типы в интерфейсах модулей Резюме
Не позволяйте типам появляться во внешнем интерфейсе модуля, если только вы не уверены в том, что все пользователи смогут корректно их понять и работать с ними. Используйте наивысший уровень абстракции, который в состоянии понять клиентский код.
Обсуждение
Чем более широко распространяется ваша библиотека, тем меньше ваш контроль над средами программирования, используемыми вашими клиентами, и тем меньше множество типов, которые ваша библиотека может надежно использовать в своем внешнем интерфейсе. Взаимодействие между модулями включает обмен бинарными данными. Увы, С++ не определяет стандартные бинарные интерфейсы; широко распространенные библиотеки для взаимодействия со внешним миром могут полагаться на такие встроенные типы, как int и char. Даже один и тот же тип на одном и том же компиляторе может оказаться бинарно несовместимым при компиляции с разными опциями.
Обычно либо вы полностью контролируете компилятор и опции компиляции, используемые для сборки модуля и его клиентов (и тогда вы можете использовать любой тип), либо вы не имеете такой возможности и должны использовать только типы, предоставляемые вычислительной платформой, или встроенные типы С++ (но даже в этом случае следует документировать размер и представление последних). В частности, использовать в интерфейсе модуля типы стандартной библиотеки можно только в том случае, если все другие модули, использующие данный, будут компилироваться в то же время и с теми же исходными файлами стандартной библиотеки.
Требуется найти определенный компромисс между проблемами используемых типов, которые могут не быть корректно восприняты всеми клиентами, и проблемами использования низкого уровня абстракции. Абстракция важна; если некоторые клиенты понимают только низкоуровневые типы и вы ограничены в использовании этими типами, то, возможно, следует подумать о дополнительных операциях, работающих с высокоуровневыми типами. Рассмотрим функцию SummarizeFile, которая получает в качестве аргумента файл. Имеется три варианта действий — передать указатель char* на строку в стиле С с именем файла; передать string с именем файла и передать объект istream или пользовательский объект Filе. Каждый из этих вариантов представляет свой уровень компромисса.
• Вариант 1. char* . Очевидно, что тип char* доступен наиболее широкому кругу клиентов. К сожалению, это также наиболее низкоуровневый вариант; в частности, он более проблематичен (например, вызывающий и вызываемый код должны явно решить, кто именно выделяет память для строки и кто ее освобождает), более подвержен ошибкам (например, файл может не существовать), и менее безопасен (например, может оказаться подвержен классической атаке, основанной на переполнении буфера).
• Вариант 2. string . Тип string доступен меньшему кругу клиентов, ограниченному использованием С++ и компиляцией с использованием той же реализации стандартной библиотеки, того же компилятора и совместимых настроек компилятора. Взамен мы получаем менее проблематичный (надо меньше беспокоиться об управлении памятью; однако см. рекомендацию 60) и более безопасный код (например, тип string увеличивает при необходимости свой буфер и не так подвержен атакам на основе переполнения буфера). Но и этот вариант относительно низкоуровневый, а потому так же открытый для ошибок, как и предыдущий (например, указанный файл может и не существовать).
• Вариант 3. istream или File . Если уж вы переходите к типам, являющимся классами, т.е. в любом случае требуется, чтобы клиент использовал язык программирования С++, причем тот же компилятор с теми же опциями компиляции, то воспользуйтесь преимуществами абстракции: класс istream (или пользовательский класс File, представляющий собой оболочку вокруг istream, позволяющую устранить зависимость от реализации стандартной библиотеки) повышает уровень абстракции и делает API существенно менее проблематичным. Функция получает объект типа File или соответствующий входной поток, она не должна заботиться об управлении памятью для строк, содержащих имя файла, и защищена от множества ошибок, которые вполне возможны при использовании первых двух вариантов. Остается только выполнить несколько проверок: файл должен быть открыт, а его содержимое иметь верный формат, но, в принципе, этим и ограничивается список неприятностей, которые могут произойти в данном варианте.
Даже если вы предпочтете воспользоваться во внешнем интерфейсе модуля низкоуровневой абстракцией, всегда используйте во внутренней реализации абстракции максимально высокого уровня и преобразуйте их в низкоуровневые абстракции на границах модуля. Например, если у вас имеются клиенты, не использующие С++, вы можете воспользоваться непрозрачным указателем void* или дескриптором типа int для работы с клиентом, но во внутренней реализации используйте высокоуровневые объекты. Преобразование между этими объектами и выбранными низкоуровневыми типами выполняйте только в интерфейсе модуля.