- •Герб Саттер
- •Как пользоваться этой книгой
- •Стандарты кодирования и вы
- •Об этой книге
- •Благодарности
- •Вопросы организации и стратегии
- •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. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
Исключения
Обмен важен для классов с семантикой значения. Существенно менее важна она для базовых классов, поскольку эти классы в любом случае используются посредством указателей (см. рекомендации 32 и 54).
Ссылки
[C++03] §17.4.3.1(1) • [Stroustrup00] §E.3.3 • [Sutter00] §12-13, §41
Пространства имен и модули
Системы имеют подсистемы, которые в свою очередь состоят из подсистем и так до бесконечности — именно поэтому мы всегда движемся сверху вниз.
— Алан Перлис (Alan Perlis)
Пространство имен — очень важный инструмент для управления именами и снижения количества коллизий имен. То же относится и к модулям, которые, помимо этого, представляют собой инструментарий для работы с версиями. Мы определим модуль как отдельный компонент программы, содержащий тесно связанные между собой ее элементы (см. рекомендацию 5) и поддерживаемый одним и тем же программистом или группой; обычно модуль всегда компилируется одним и тем же компилятором с использованием одних и тех же опций. Модули имеются на разных уровнях детализации в широком диапазоне размеров. С одной стороны, модуль может быть минимального размера, представляя собой отдельный объектный файл, содержащий только один класс; с другой стороны, он может быть, например, отдельной динамической библиотекой, генерируемой из множества исходных файлов, содержимое которых образует подсистему внутри приложения большего размера или выпускается отдельно. Модуль может даже представлять собой огромную библиотеку, состоящую из множества небольших модулей (статических или динамических библиотек), содержащих тысячи разных типов. Несмотря на то, что такие библиотеки в стандарте С++ не упоминаются, программисты постоянно создают и используют библиотеки, и хорошо продуманная модуляризация является фундаментальной частью успешного управления зависимостями (см., например, рекомендацию 11).
Трудно представить себе программу значительного размера, которая не использует как пространства имен, так и модули. В этом разделе мы рассмотрим основные рекомендации по использованию двух этих взаимосвязанных инструментов, наряду с их взаимодействием с другими частями языка программирования и среды времени выполнения. Эти рекомендации помогут вам наиболее эффективно воспользоваться таким мощным инструментарием и избежать возможных неприятностей.
В этом разделе мы считаем наиболее значимой рекомендацию 58 — "Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы".
57. Храните типы и их свободный интерфейс в одном пространстве имен Резюме
Функции, не являющиеся членами и разработанные как часть интерфейса класса X (в особенности операторы и вспомогательные функции), должны быть определены в том же пространстве имен, что и X, что обеспечивает их корректный вызов.
Обсуждение
Открытый интерфейс класса образуют не только открытые функции-члены, но и функции, не являющиеся членами. Принцип Интерфейса гласит: для класса X все функции (включая функции, не являющиеся членами), которые "упоминают X" и "поставляются вместе с X" в одном и том же пространстве имен, являются логической частью X, поскольку образуют часть интерфейса X (см. рекомендацию 44 и [Sutter00]).
Язык С++ спроектирован с явным учетом Принципа Интерфейса. Причина, по которой в язык добавлен поиск, зависящий от аргумента (argument-dependent lookup — ADL), известный также как поиск Кёнига, заключается в том, чтобы обеспечить коду, использующему объект x типа X, возможность работать с частью его интерфейса, состоящей из функций, не являющихся членами (например, инструкция cout << x использует оператор operator<<, который не является членом класса X) так же легко, как и функции-члены (например, вызов x.f()) не требует выполнения специального поиска, поскольку очевидно, что поиск f выполняется в области видимости X). ADL обеспечивает для свободных функций, которые получают объект X в качестве аргумента и поставляются вместе с определением X ту же простоту использования, что и для функций-членов интерфейса X. Одним из главных мотивов принятия ADL был, в частности, класс std::string (см. [Sutter00]).
Рассмотрим класс X, определенный в пространстве имен N:
class X {
publiс:
void f();
};
X operator+(const X&, const X&);
В вызывающей функции обычно пишется код наподобие x3=x1+x2, где x1, x2 и x3 — объекты типа X. Если оператор operator+ объявлен в том же пространстве имен, что и X, никаких проблем не возникает, и такой код отлично работает, поскольку оператор operator+ будет легко найден с помощью ADL.
Если же оператор operator+ не объявлен в том же пространстве имен, что и X, вызывающий код работать не будет. В этом случае имеется два способа заставить его заработать. Первый состоит в использовании явно квалифицированного оператора
x3 = N::operator+(x1, x2);
Грустная картина — невозможность использовать естественный синтаксис оператора, который, собственно, и был главной целью введения перегрузки операторов в язык программирования. Другой способ заставить работать приведенный ранее код — использовать инструкцию using:
using N::operator+;
// или: using namespace N;
x3 = x1 + x2;
Применение using — совершенно нормальная и приемлемая вещь (см. рекомендацию 59), но все проблемы решаются гораздо проще, если автор X изначально поступает корректно и помещает оператор operator+, работающий с объектами X, в то же пространство имен, где находится X.
"Оборотная сторона" этого вопроса рассматривается в рекомендации 58.