Пользуйтесь правильным алгоритмом сортировкиРезюме
При сортировке вы должны четко понимать, как работает каждый из сортирующих алгоритмов, и использовать наиболее дешевый среди тех, которые пригодны для решения вашей задачи.
Делайте предикаты чистыми функциямиРезюме
Предикат представляет собой функциональный объект, который возвращает ответ да/нет, обычно в виде значения типа bool. Функция является "чистой" в математическом смысле, если ее результат зависит только от ее аргументов (обратите внимание — в данном случае термин "чистая" не имеет никакого отношения к чисто виртуальным функциям).
Не позволяйте предикатам сохранять или обращаться к состоянию так, чтобы это могло влиять на результат работы оператора operator(); при этом понятие состояния включает как данные-члены, так и глобальные состояния. Для предикатов желательно делать оператор operator() константной функцией-членом (см. рекомендацию 15).
В качестве аргументов алгоритмов
Предпочтительно передавать алгоритмам функциональные объекты, а не функции, а компараторы ассоциативных контейнеров просто должны быть функциональными объектами. Функциональные объекты адаптируемы и, вопреки ожиданиям, обычно дают более быстрый по сравнению с функциями код.
Корректно пишите функциональные объектыРезюме
Разрабатывайте функциональные объекты так, чтобы их копирование выполнялось как можно эффективнее. Там, где это возможно, делайте их максимально адаптируемыми путем наследования от unary_function или binary_function.
Избегайте явного выбора типов — используйте полиморфизмРезюме
Избегайте явного выбора типа объекта для настройки поведения. Используйте шаблоны и виртуальные функции для того, чтобы поведение объекта определялось его типом, а не вызывающим кодом.
Работайте с типами, а не с представлениямиРезюме
Не пытайтесь делать какие-то предположения о том, как именно объекты представлены в памяти. Как именно следует записывать и считывать объекты из памяти — пусть решают типы объектов.
Избегайте reinterpret_castРезюме
Как гласит римская пословица, у лжи короткие ноги. Не пытайтесь использовать reinterpret_cast, чтобы заставить компилятор рассматривать биты объекта одного типа как биты объекта другого типа. Такое действие противоречит безопасности типов.
Избегайте применения static_cast к указателямРезюме
К указателям на динамические объекты не следует применят преобразование static_cast. Используйте безопасные альтернативы — от dynamic_cast до перепроектирования.
Избегайте преобразований, отменяющих constРезюме
Преобразование типов, отменяющее const, может привести к неопределенному поведению, а кроме того, это свидетельство плохого стиля программирования даже в том случае, когда применение такого преобразования вполне законно.
Не используйте преобразование типов в стиле СРезюме
Возраст не всегда означает мудрость. Старое преобразование типов в стиле С имеет различную (и часто опасную) семантику в зависимости от контекста, спрятанную за единым синтаксисом. Замена преобразования типов в стиле С преобразованиями C++ поможет защититься от неожиданных ошибок.
Не применяйте memcpy или memcmp к не-POD типамРезюме
Не работайте рентгеновским аппаратом (см. рекомендацию 91). Не используйте memcpy и memcmp для копирования или сравнения чего-либо структурированного более, чем обычная память.
Не используйте объединения для преобразованийРезюме
Хитрость все равно остается ложью: объединения можно использовать для получения "преобразования типа без преобразования", записывая информацию в один член и считывая из другого. Однако это еще более опасно и менее предсказуемо, чем применение reinterpret_cast (см. рекомендацию 92).
Не используйте неизвестные аргументы (троеточия)Резюме
Наличие троеточий в C++ — опасное наследие С. Избегайте их в своих программах; используйте вместо этого высокоуровневые конструкции и библиотеки C++.
Не используйте недействительные объекты и небезопасные функцииРезюме
Вы же не используете просроченные лекарства? И недействительные объекты, и "антикварные", но небезопасные функции способны навредить здоровью ваших программ.
Не рассматривайте массивы полиморфноРезюме
Полиморфная работа с массивами — большая ошибка. К сожалению, обычно компилятор никак на нее не реагирует. Не попадайтесь в эту ловушку!
Безопасность типов
Если вы лжете компилятору, он будет мстить.
— Генри Спенсер (Henry Spencer)
Всегда будут вещи, которые мы будем хотеть сказать в наших программах и которые трудно сформулировать на любом языке программирования.
— Алан Перлис (Alan Perlis)
Последней (не по важности) темой книги является корректность типов — очень важное свойство программ, которое вы должны изо всех сил стараться поддерживать. Теоретически корректная с точки зрения типов функция не может обратиться к нетипизированной памяти или вернуть неверные значения. На практике, если ваш код поддерживает корректность типов, он тем самым избегает большого количества неприятных ошибок, от непереносимости программ до порчи содержимого памяти и неопределенного поведения программ.
Основная идея поддержки корректности типов — всегда считывать информацию в том формате, в котором она была записана. Иногда C++ позволяет легко нарушить это правило; приведенные в этом разделе рекомендации помогут вам избежать подобных ошибок.
В этом разделе мы считаем наиболее значимой рекомендацию 91 — "Работайте с типами, а не с представлениями". Система типов — ваш друг и верный союзник. Воспользуйтесь ее помощью и попытайтесь не злоупотреблять ее доверием.
Стр.188
Безопасность типов
90. Избегайте явного выбора типов — используйте полиморфизм
Избегайте явного выбора типа объекта для настройки поведения. Используйте шаблоны и виртуальные функции для того, чтобы поведение объекта определялось его типом, а не вызывающим кодом.
91. Работайте с типами, а не с представлениями
Не пытайтесь делать какие-то предположения о том, как именно объекты представлены в памяти. Как именно следует записывать и считывать объекты из памяти — пусть решают типы объектов.
92. Избегайте reinterpret_cast
Как гласит римская пословица, у лжи короткие ноги. Не пытайтесь использовать reinterpret_cast, чтобы заставить компилятор рассматривать биты объекта одного типа как биты объекта другого типа. Такое действие противоречит безопасности типов.
93. Избегайте применения static_cast к указателям
К указателям на динамические объекты не следует применят преобразование static_cast. Используйте безопасные альтернативы — от dynamic_cast до перепроектирования.
94. Избегайте преобразований, отменяющих const
Преобразование типов, отменяющее const, может привести к неопределенному поведению, а кроме того, это свидетельство плохого стиля программирования даже в том случае, когда применение такого преобразования вполне законно.
95. Не используйте преобразование типов в стиле С
Возраст не всегда означает мудрость. Старое преобразование типов в стиле С имеет различную (и часто опасную) семантику в зависимости от контекста, спрятанную за единым синтаксисом. Замена преобразования типов в стиле С преобразованиями C+ + поможет защититься от неожиданных ошибок.
96. Не применяйте memcpy или memcmp к не-POD типам
Не работайте рентгеновским аппаратом (см. рекомендацию 91). Не используйте тетеру и тетстр для копирования или сравнения чего-либо структурированного более, чем обычная память.
97. Не используйте объединения для преобразований
Хитрость все равно остается ложью: объединения можно использовать для получения "преобразования типа без преобразования ", записывая информацию в один член и считывая из другого. Однако это еще более опасно и менее предсказуемо, чем применение reinterpret_cast (см. рекомендацию 92).
98. Не используйте неизвестные аргументы (троеточия)
Наличие троеточий в C++ — опасное наследие С. Избегайте их в своих программах; используйте вместо этого высокоуровневые конструкции и библиотеки C++.
99. Не используйте недействительные объекты и небезопасные функции
Вы оке не используете просроченные лекарства? И недействительные объекты, и "антикварные ", но небезопасные функции способны навредить здоровью ваших программ.
100. Не рассматривайте массивы полиморфно
Полиморфная работа с массивами — большая ошибка. К сожалению, обычно компилятор никак на нее не реагирует. Не попадайтесь в эту ловушку!
Стр.219
Благодарности
Мы от всей души благодарны редактору серии Бьярну Страуструпу (Bjarne Stroustrup), редакторам Питеру Гордону (Peter Gordon) и Дебби Лафферти (Debbie Lafferty), а также Тирреллу Албаху (Tyrrell Albaugh), Ким Бодихаймер (Kim Boedigheimer), Джону Фуллеру (John Fuller), Бернарду Гаффни (Bernard Gaffney), Курту Джонсону (Curt Johnson), Чанде Лири-Коту (Chanda Leary-Coutu), Чарли Ледди (Charles Leddy), Хитеру Муллэйн (Heather Mullane), Чути Прасертсиху (Chuti Prasertsith), Ларе Вайсонг (Lara Wysong) и всем остальным работникам издательства Addison-Wesley, помогавшим нам в нашей работе над этим проектом. Нам было очень приятно работать с ними.
На идею и оформление книги нас натолкнули несколько источников, включая такие, как [Cline99], [Peters99], а также работы легендарного и широко цитируемого Алана Перлиса (Alan Perlis).
Особая благодарность тем людям, чьи отзывы помогли нам сделать многие части книги намного лучшими, чем они были бы без этих замечаний. Особенно большое влияние на книгу оказали острые комментарии Бьярна Страуструпа. Мы очень хотим поблагодарить за активное участие в обсуждении материала и высказанные замечания таких людей, как Дэйв Абрамс (Dave Abrahams), Маршалл Клайн (Marshall Cline), Кевлин Хенни (Kevlin Henney), Говард Хиннант (Howard Hinnant), Джим Хайслоп (Jim Hyslop), Николаи Джосаттис (Nicolai Josuttis), Йон Калб (Jon Kalb), Макс Хесин (Max Khesin), Стен Липпман (Stan Lippman), Скотт Мейерс (Scott Meyers) и Дэвид Вандевурд (Daveed Vandevoorde). Кроме того, отдельное спасибо хотелось бы сказать Чаку Аллисону (Chuck Allison), Самиру Байяю (Samir Bajaj), Марку Барбуру (Маге Barbour), Тревису Брауну (Travis Brown), Нилу Кумбесу (Nil Coombes), Дамиану Дечеву (Damian Dechev), Стиву Дьюхарсту (Steve Dewhurst), Питеру Димову (Peter Dimov), Аттиле Фехеру (Attila Feher), Алану Гриффитсу (Alan Griffiths), Мичи Хеннингу (Michi Henning), Джеймсу Канзе (James Kanze), Бартошу Милевски (Bartosz Milewski), Мэтту Маркусу (Mart Marcus), Балогу Палу (Balog Pal), Владимиру Прусу (Vladimir Prus), Дэну Саксу (Dan Saks), Люку Вагнеру (Luke Wagner), Мэтью Вильсону (Matthew Wilson) и Леору Золману (Leor Zolman).
Как обычно, все оставшиеся в книге ошибки и недосмотры — на нашей совести, а не на их.
Герб Саттер (Herb Sutter)
Андрей Александреску (Andrei Alexandrescu)
Сиэтл, сентябрь 2004
Стр.13
Функции и операторы
Если ваша процедура имеет десять параметров — вероятно, вы где-то ошиблись.
— Алан Перлис (Alan Perlis)
Функции, включая перегруженные операторы, представляют собой фундаментальные единицы работы. Как вы увидите позже в разделе "Обработка ошибок и исключения" (в частности, в рекомендации 70), это непосредственно влияет на наши рассуждения о корректности и безопасности кода.
Но давайте сначала рассмотрим некоторые фундаментальные вопросы написания функций, в том числе операторов. В частности, мы обратим особое внимание на их параметры, семантику и перегрузку.
В этом разделе наиболее важной нам представляется рекомендация 26 — "Сохраняйте естественную семантику перегруженных операторов".
Стр.58
Функции и операторы
25. Передача параметров по значению, (интеллектуальному) указателю или ссылке
Вы должны четко уяснить разницу между входными, выходными параметрами и параметрами, предназначенными и для ввода, и для вывода информации, а также между передачей параметров по значению и по ссылке, и корректно их использовать.
26. Сохраняйте естественную семантику перегруженных операторов
Программисты ненавидят сюрпризы. Перегружайте операторы только в случае веских на то оснований, и сохраняйте при этом их естественную семантику. Если это оказывается сложным, возможно, вы неверно используете перегрузку операторов.
27. Отдавайте предпочтение каноническим формам арифметических операторов и операторов присваивания
Если можно записать а+b, то необходимо, чтобы можно было записать и а+=Ь. При определении бинарных арифметических операторов одновременно предоставляйте и их присваивающие версии, причем делайте это с минимальным дублированием и максимальной эффективностью.
28. Предпочитайте канонический вид ++ и --, и вызов префиксных операторов
Особенность операторов инкремента и декремента состоит в том, что у них есть префиксная и постфиксная формы с немного отличающейся семантикой. Определяйте операторы operator++ и operator— так, чтобы они подражали поведению своих встроенных двойников. Если только вам не требуется исходное значение — используйте префиксные версии операторов.
29. Используйте перегрузку, чтобы избежать неявного преобразования типов
Не преумножайте объекты сверх необходимости (Бритва Оккама): неявное преобразование типов обеспечивает определенное синтаксическое удобство (однако см. рекомендацию 40), но в ситуации, когда допустима оптимизация (см. рекомендацию 8) и желательно избежать создания излишних объектов, можно обеспечить перегруженные функции с сигнатурами, точно соответствующими распространенным типам аргументов, и тем самым избежать преобразования типов.
30. Избегайте перегрузки &&, II и , (запятой)
Мудрость — это знание того, когда надо воздержаться. Встроенные операторы &&, 11 и , (запятая) трактуются компилятором специальным образом. После перегрузки они становятся обычными функциями с весьма отличной семантикой (при этом вы нарушаете рекомендации 26 и 31), а это прямой путь к трудноопределимым ошибкам и ненадежности. Не перегружайте эти операторы без крайней необходимости и глубокого понимания.
31. Не пишите код, который зависит от порядка вычислений аргументов функции
Порядок вычисления аргументов функции не определен, поэтому никогда не полагайтесь на то, что аргументы будут вычисляться в той или иной очередности.
Как пользоваться этой книгой
Думать. Надо добросовестно следовать умным советам, но делать это не вслепую. Во многих разделах этой книги есть подраздел "Исключения", в котором приводятся нестандартные, редко встречающиеся ситуации, когда совет из основного раздела может оказаться неприменим. Никакое количество даже самых хороших (мы на это надеемся) советов не могут заменить голову.
Каждая команда разработчиков отвечает за принятие собственных стандартов и несет ответственность за них и за неукоснительное следование им. Ваша команда — не исключение. Если вы — руководитель, предложите членам своей группы поучаствовать в разработке стандартов, которым группа будет следовать в своей работе. Люди всегда охотнее следуют правилам, которые они сами для себя вырабатывают, чем тем, которые им навязаны.
Стр.10
Эта книга предназначена для того, чтобы послужить основой для вашего стандарта кодирования и быть в той или иной мере включенной в него. Это — не ultima ratio в стандартах кодирования, и ваша группа может разработать (или прибавить) свои правила, в наибольшей степени подходящие для вашей конкретной группы или для конкретной решаемой задачи, так что вы не должны быть скованы этой книгой по рукам и ногам. Тем не менее, мы надеемся, что эта книга поможет вам сберечь время и усилия при разработке собственного стандарта кодирования, а также будет способствовать повышению его качества и последовательности.
Ознакомьте членов своей команды с этой книгой, и после того, как они полностью прочтут ее и познакомятся со всеми рекомендациями и их обоснованиями, решите, какие из них подходят вам, а какие в силу особенностей вашего проекта для вас неприменимы. После этого строго придерживайтесь выбранной стратегии. После того как командный стандарт принят, он не должен нарушаться иначе как по согласованному решению всей команды в целом.
И наконец, периодически творчески пересматривайте собственные стандарты с учетом практического опыта, приобретенного всей командой при работе над проектом.
Конструкторы, деструкторы и копирование
Если стандарт привел вас к обрыву, это еще не значит, что вы должны прыгнуть с него.
— Норман Даймонд (Norman Diamond)
О Большой Четверке специальных функций было сказано достаточно, чтобы вы не удивлялись тому, что им посвящен отдельный раздел. Здесь собраны знания и практика, связанные с конструкторами по умолчанию, копирующими конструкторами, копирующим присваиванием и деструкторами.
Одна из причин, по которым при работе с этими функциями следует быть особенно внимательными, заключается в том, что если вы дадите компилятору хотя бы полшанса — он тут же напишет эти функции за вас. Еще одна причина состоит в том, что C++ по умолчанию рассматривает классы как типы-значения, но далеко не все типы именно таковы (см. рекомендацию 32). Надо отчетливо понимать, когда следует писать (или запрещать) эти специальные функции явно, и следовать правилам и рекомендациям из этого раздела — это поможет вам в написании корректного, расширяемого и безопасного кода.
В этом разделе мы считаем наиболее значимой рекомендацию 51 — "Деструкторы, функции освобождения ресурсов и обмена не ошибаются".
Стр.100
Конструкторы, деструкторы и копирование
47. Определяйте и инициализируйте переменные-члены в одном порядке
Переменные-члены всегда инициализируются в том порядке, в котором они объявлены при определении класса; порядок их упоминания в списке инициализации конструктора игнорируется. Убедитесь, что в коде конструктора указан тот же порядок, что и в определении класса.
48. В конструкторах предпочитайте инициализацию присваиванию
В конструкторах использование инициализации вместо присваивания для установки значений переменных-членов предохраняет от ненужной работы времени выполнения при том же объеме вводимого исходного текста.
Стр.212
49. Избегайте вызовов виртуальных функций в конструкторах и деструкторах
Внутри конструкторов и деструкторов виртуальные функции теряют виртуальность. Хуже того — все прямые или косвенные вызовы нереализованных чисто виртуальных функций из конструктора или деструктора приводят к неопределенному поведению. Если ваш дизайн требует виртуальной передачи в производный класс из конструктора или деструктора базового класса, следует воспользоваться иной методикой, например, постконструкторами.
50. Делайте деструкторы базовых классов открытыми и виртуальными либо защищенными и невиртуальными
Удалять или не удалять — вот в чем вопрос! Если следует обеспечить возможность удаления посредством указателя на базовый класс, то деструктор базового класса должен быть открытым и виртуальным. В противном случае он должен быть защищенным и невиртуальным.
51. Деструкторы, функции освобождения ресурсов и обмена не ошибаются
Все запуски этих функций должны быть успешными. Никогда не позволяйте ошибке выйти за пределы деструктора, функции освобождения ресурса (например, оператора delete) или функции обмена. В частности, типы, деструкторы которых могут генерировать исключения, категорически запрещено использовать со стандартной библиотекой C++.
52. Копируйте и ликвидируйте согласованно
Если вы определили одну из следующего списка функций — копирующий конструктор, оператор копирующего присваивания или деструктор — вероятно, вам потребуется определить и обе оставшиеся функции (или по крайней мере одну из них).
53. Явно разрешайте или запрещайте копирование
Копируйте со знанием дела: тщательно выбирайте между использованием сгенерированных компилятором копирующего конструктора и оператора присваивания, написанием собственных версий или явным запрещением обоих, если копирование не должно быть разрешено.
54. Избегайте срезки. Подумайте об использовании в базовом классе клонирования вместо копирования
Срезка объектов происходит автоматически, невидимо и может приводить к полному разрушению чудесного полиморфного дизайна. Подумайте о полном запрете копирующего конструктора и копирующего присваивания в базовых классах. Вместо них можно использовать виртуальную функцию-член Clone, если пользователям вашего класса необходимо получать полиморфные (полные, глубокие) копии.
55. Предпочитайте канонический вид присваивания
При реализации оператора operator= предпочитайте использовать канонический вид — невиртуальный с определенной сигнатурой.
56. Обеспечьте бессбойную функцию обмена
Обычно имеет смысл предоставить для класса функцию swap в целях эффективного и бессбойного обмена внутреннего содержимого объекта с внутренним содержимым другого объекта. Такая функция может пригодиться для реализации ряда идиом, от простого перемещения объектов до реализации присваивания, легко обеспечивающего функцию принятия результатов работы со строгими гарантиями безопасности для вызывающего кода (см. также рекомендацию 51).
Стр.213
Об этой книге
Основными принципами дизайна данной книги являются следующие.
Краткость — сестра таланта. Чем больше стандарт кодирования по размеру, тем больше шансов, что он будет благополучно проигнорирован. Читают и используют обычно короткие стандарты. Длинные разделы, как правило, просто просматривают "по диагонали", короткие статьи обычно удостаиваются внимательного прочтения.
Никакой материал не должен вызывать дискуссий. Эта книга документирует широко используемые стандарты, а не изобретает их. Если некоторая рекомендация не применима во всех ситуациях, то мы так и пишем — "подумайте о применении X" вместо "делайте X". Кроме того, к каждому правилу указаны все общепринятые исключения.
Весь материал должен быть обоснован. Все рекомендации в этой книге взяты из существующих печатных работ. В конце книги приведен список использованной литературы по C++.
Материал не должен быть банален. Мы не даем рекомендации, которым вы и так следуете, которые обеспечиваются компилятором или которые уже изложены в других разделах.
Например, "не возвращайте указатель/ссылку на локальную переменную" — хороший совет, но он не включен в данную книгу, поскольку практически все компиляторы выдают соответствующее предупреждение, к тому же этот вопрос раскрывается в первом разделе книги.
Рекомендация "используйте редактор (компилятор, отладчик)" — тоже хороший совет, но, конечно, вы используете эти инструменты и без нашего напоминания. Вместо этого мы рекомендуем использовать автоматизированные системы сборки программ и системы управления версиями.
Еще один совет — "не злоупотребляйте goto" — исходя из нашего опыта, и так широко известен всем программистам, так что нет смысла повторяться.
Каждый раздел состоит из следующих частей.
Заглавие. Краткое название раздела, поясняющее, о чем будет идти речь.
Резюме. Краткое изложение сути вопроса.
Стр.12
Обсуждение. Расширенное пояснение рекомендации. Зачастую включает краткое обоснование, но учтите, что полную информацию по данному вопросу следует искать в приведенных ссылках.
Примеры (если таковые имеются). Примеры, демонстрирующие правило или позволяющие лучше его понять и запомнить.
Исключения (если таковые имеются). Описание ситуаций (обычно редких), когда приведенное правило неприменимо. Однако остерегайтесь попасть в ловушку, думая, что ваш случай особый и что в вашей ситуации это правило неприменимо,— обычно при здравом размышлении оказывается, что ничего особого в вашей ситуации нет и описанное правило может быть с успехом вами применено.
Ссылки. В приведенной в этом подразделе литературе по C++ вы найдете более полный анализ рассматриваемого в разделе вопроса.
В каждой части книги имеется "наиболее важный раздел" — обычно это первый раздел части. Однако иногда это правило нарушалось в связи с необходимостью последовательного и связного изложения материала.
Обработка ошибок и исключения
Обработка ошибок — сложная задача, при решении которой программисту требуется вся помощь, которая только может быть предоставлена.
— Бьярн Страуструп (Bjarne Stroustrup),
[Stroustrup94] §16.2
Имеется три способа написать программу без ошибок; но работает только третий способ.
— Алан Перлис (Alan Perlis)
Вопрос не в том, будем ли мы делать программные ошибки. Вопрос в том, будем ли мы что-либо предпринимать, чтобы позволить компилятору и другим используемым инструментам их обнаружить.
В этом разделе документированы добытые трудом множества программистов знания и наилучшие практические подходы, некоторые из которых стоили многих лет работы. Следуйте приведенным правилам и рекомендациям. Когда мы пишем сложную, надежную и безопасную программу — нам требуется вся помощь, которую мы только в состоянии получить.
В этом разделе мы считаем наиболее значимой рекомендацию 69 — "Определите разумную стратегию обработки ошибок и строго ей следуйте".
Стр.144
Обработка ошибок и исключения
68.Широко применяйте assert для документирования внутренних допущений и инвариантов
Используйте assert или его эквивалент для документирования внутренних допущений в модуле (т.е. там, где вызываемый и вызывающий код поддерживаются одним и тем же программистом или командой), которые должны всегда выполняться (в противном случае они являются следствием программной ошибки; например, нарушение постусловий функции, обнаруженное вызывающим кодом). (См. также рекомендацию 70.) Убедитесь, что использование assert не приводит к побочным действиям.
69. Определите разумную стратегию обработки ошибок и строго ей следуйте
Еще на ранней стадии проектирования разработайте практичную, последовательную и разумную стратегию обработки ошибок и строго следуйте ей. Убедитесь, что ваша стратегия включает следующее.
Идентификация: какие условия являются ошибкой.
Строгость: насколько важна каждая ошибка.
Обнаружение: какой код отвечает за обнаружение ошибки.
Распространение: какой механизм используется для описания и распространения уведомления об ошибке в каждом модуле.
Обработка: какой код отвечает за выполнение действий, связанных с ошибкой.
Уведомление: каким образом информация об ошибке вносится в журнальный файл или производится уведомление пользователя программы.
Изменяйте механизмы обработки ошибок только в пределах границ модуля.
Стр.215
70. Отличайте ошибки от ситуаций, не являющихся ошибками
Функция представляет собой единицу работы. Таким образом, сбои следует рассматривать либо как ошибки, либо как штатные ситуации, в зависимости от их влияния на функции. В функции/сбой является ошибкой тогда и только тогда, когда он нарушает одно из предусловий/, не позволяет выполнить предусловие вызываемой ею функции, препятствует достижению собственных постусловий f или сохранению инварианта, за поддержку которого отвечает функция/.
В частности, в этой рекомендации мы исключаем внутренние программные ошибки (т.е. те, где за вызывающий и вызываемый код отвечает один и тот же человек или команда, — например, в пределах одного модуля). Они представляют собой отдельную категорию ошибок, для работы с которой используется такое средство, как проверки (см. рекомендацию 68).
71. Проектируйте и пишите безопасный в отношении ошибок код
В каждой функции обеспечивайте наиболее строгую гарантию безопасности, какой только можно добиться без дополнительных затрат со стороны вызывающего кода, не требующего такого уровня гарантии. Всегда обеспечивайте, как минимум, базовую гарантию безопасности.
Убедитесь, что при любых ошибках ваша программа всегда остается в корректном состоянии (в этом и заключается базовая гарантия). Остерегайтесь ошибок, нарушающих инвариант (включая утечки, но не ограничиваясь ими).
Желательно дополнительно гарантировать, что конечное состояние либо является исходным состоянием (в результате отката после происшедшей ошибки), либо корректно вычисленным целевым состоянием (если ошибок не было). Это — строгая гарантия безопасности.
Еще лучше гарантировать, что сбой в процессе операции невозможен. Хотя для большинства функций это невозможно, такую гарантию следует обеспечить для таких функций, как деструкторы и функции освобождения ресурсов. Данная гарантия — гарантия бес-сбойности.
72. Для уведомления об ошибках следует использовать исключения
Для уведомления об ошибках лучше использовать механизм исключений, а не коды ошибок. Применять коды состояния (например, коды ошибок, переменную еггпо) следует только тогда, когда нельзя использовать исключения (см. рекомендацию 62), а также для ситуаций, которые не являются ошибками. К другим методам, таким как экстренное завершение программы (или плановое завершение с освобождением ресурсов и т.п. действиями), следует прибегать только в ситуациях, когда восстановление после ошибки невозможно (или не требуется).
73. Генерируйте исключения по значению, перехватывайте — по ссылке
Генерируйте исключения по значению (не через указатель) и перехватывайте их как ссылки (обычно константные). Эта комбинация наилучшим образом соответствует семантике исключений. При повторной генерации перехваченного исключения предпочтительно использовать просто оператор throw;, а не инструкцию throw e;.
74. Уведомляйте об ошибках, обрабатывайте и преобразовывайте их там, где следует
Сообщайте об ошибках в тот момент, когда они обнаружены и идентифицированы как ошибки. Обрабатывайте или преобразовывайте их на самом нижнем уровне, на котором это можно сделать корректно.
Стр.216
75. Избегайте спецификаций исключений
Не пишите спецификаций исключений у ваших функций, если только вас не заставляют это делать внешние обстоятельства (например, код, который вы не можете изменить, уже ввел их; см. исключения к данному разделу).
От издательского дома "Вильямс"
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мнение и хотим знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услышать и любые другие замечания, которые вам хотелось бы высказать в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бумажное или электронное письмо либо просто посетить наш Web-сервер и оставить свои замечания там. Одним словом, любым удобным для вас способом дайте нам знать, нравится ли вам эта книга, а также выскажите свое мнение о том, как сделать наши книги более интересными для вас.
Посылая письмо или сообщение, не забудьте указать название книги и ее авторов, а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением и обязательно учтем его при отборе и подготовке к изданию последующих книг. Наши координаты:
E-mail: info@williamspublishing.com
WWW: http://www.wiliamspublishing.com
Информация для писем из
России: 115419, Москва, а/я 783
Украины: 03150, Киев, а/я 152
Стр.220
делайте одинаковые вещи одинаковыми способами.
Идите проторенной дорогой — делайте одинаковые вещи одинаковыми способами. Накапливайте идиомы. Стандартизируйте. Единственное отличие между вами и Шекспиром — в количестве используемых идиом, а не в размере словаря.
— Алан Перлис (Alan Perlis) [выделено нами]
Лучшее в стандарте то, что он предоставляет богатый выбор.
— Приписывается разным людям
Мы бы хотели, чтобы эта книга стала основой для стандартов кодирования, используемых вашей командой, по двум основным причинам.
Стандарты кодирования должны отражать лучший опыт проб и ошибок всего сообщества программистов. В них должны содержаться проверенные идиомы, основанные на опыте и твердом понимании языка. В частности, стандарт кодирования должен основываться на исчерпывающем анализе литературы по разработке программного обеспечения, и объединять воедино правила, рекомендации и наилучшие практические решения, которые в противном случае оказываются разбросанными по многочисленным источникам.
Природа не терпит пустоты. Если вы не разработаете набор правил, то это сделает кто-то другой. Такие "самопальные" стандарты, как правило, грешат тем, что включают нежелательные для стандарта требования; например, многие из них, по сути, заставляют программистов использовать C++ просто как улучшенный С.
Множество таких плохих стандартов кодирования разработаны людьми, которые недостаточно хорошо понимают язык программирования C++ или пытаются чрезмерно детализировать его применение. Плохой стандарт кодирования быстро теряет кредит доверия, и в результате несогласие или неприятие программистами части его положений распространяется на весь стандарт целиком, перечеркивая содержащиеся в нем различные положительные советы и рекомендации. И это — в лучшем случае, потому что в худшем случае такой стандарт и его выполнение могут быть навязаны руководством.
Предметный указатель
#
#include, 45
А
Abelson, Harold, 25
ADL, см. Поиск, зависящий от аргумента assert, 45; 112; 144
auto_ptr, 168
В
Bell, Gordon, 25
Bentley, Jon, 25; 29
binary_search, 179
С
Cargill, Tom, 92
const, 42; 142; 194
У входных параметров функций, 58
CVS, 20
D
delete, 95
Dependency inversion principle, 54; 77
deque, 164; 171
Diamond, Norman, 99
dynamic_cast, 41; 193
E
equal_range, 179
errno, 154
explicit, 85; 110
F
find,179
fmd_if, 179
for_each, 28
I
inline, 30
К
Knuth, Donald, 23; 29
L
list, 164
lower_bound, 179
M
map, 164
McConnell, Steve, 25; 144
memcmp, 197
memcpy, 197
mutable, 42
N
new, 95; 97
Размещающий, 98
nth_element, 180
P
partial_sort, 180
partition, 180
Perlis, Alan, 23; 39; 57; 117; 143; 187
Plain Old Data, 190
POD, 190
push_back, 28; 169
R
RAII, 37
realloc, 24
reinterpret_cast, 192
return, 17
Стр.221
s
Schwarz, Jerry, 127
set, 164
shared_ptr, 125; 168
sort, 180
Spencer, Henry, 187
stable_partition, 180
stable_sort, 180
static_cast, 41; 193
string, 167; 171
Stroustrup, Bjarne, 69; 133; 143; 163; 173
struct, 33
Sussman, Gerald Jay, 25
swap, 114; 140
U
upper_bound, 179
using, 119; 122
V
vector, 164; 166; 167; 171
volatile, 49
A
Абельсон, Гарольд, 25
Абстрактный интерфейс, 77; 188
Агрегаты значений, 88
Алгоритмы STL, 173
Аргумент по умолчанию, 81
Атомарные операции, 34
Б
Белл, Гордон,25
Бентли, Ион, 25; 29
В
Венгерская запись, 15
Взаимоблокировка, 34; 36
Вложенность блоков, 50
Внешнее связывание, 32
Время жизни, 47; 200
Г
Гарантии безопасности, 151
Глобальные объекты, 32
Д
Даймонд, Норман, 99
Декремент
Префиксная и постфиксная формы, 62
Дескриптор, 89
Деструктор, 106;151
Виртуальный, 76; 104
Динамическое связывание, 134
Дружба, 73
З
Зависимости определений, 53
Зависимые имена, 55; 138
Заголовочный файл, 16; 55
Защита от множественного включения, 56
Закон Второго Шанса, 78
И
Идиома
Erase-remove, 171
Pimpl,42;73;87;91; 114; 186
RAII, 17; 37; 108; 109
Бессбойного принятия работы, 155
Выделение ресурса есть инициализация,
См. RAII
Индексного контейнера, 180
Клонирования, 111
Копирующего присваивания, 107
Присваивания через обмен, 114
Реализации оператора через присваивающую версию, 60
Реализации постфиксного оператора через префиксный, 62
Устранения излишней емкости контейнера, 171
Имя
Зависимое, 138
Инвариант, 148
Инициализация, 17; 32; 48; 100; 101
Порядок, 52
Инкапсуляция, 83; 87
Инкремент
Префиксная и постфиксная формы, 62
Интеллектуальный указатель, 38; 168
Интерфейс, 130
Абстрактный, 77
Неявный, 134
Открытый, 83; 118
Исключение, 71; 106; 128; 146; 154; 158
Генерация, 158
Повторная генерация, 158
Преобразование, 159
Стр.222
Итератор, 142
Недействительный, 175
К
Каргилл, Том, 92
Класс, 69
Базовый, 70; 74; 75; 84; 105
Вспомогательный, 71
Инициализация, 100; 101
Исключения, 71
Класс-значение, 70
Минимальный, 72
Множественное наследование, 78
Монолитный, 72
Производный, 81
Пустой базовый, 74; 78
Свойств, 70
Стратегии, 70; 80; 105
Кнут, Дональд, 23; 29
Код состояния, 154
Командная работа, 21; 131
Комментарий, 14
Композиция, 73
Конструктор, 155
Неявно преобразующий, 85
Копирование, 109
Копирующее присваивание, 152
Копирующий конструктор, 37
Л
Лямбда-функция, 177
М
Мак-Коннелл, Стив, 25; 144
Макрос, 44
Массив, 201
Масштабируемость, 27; 31; 32
Минимизация зависимостей, 33
Многопоточность, 34
Взаимоблокировка, 34
Внешняя блокировка, 35
Внутренняя блокировка, 35
Условия гонки, 34
Множественное наследование, 78
Модульность, 77
Н
Наследование, 73; 75
Открытое, 79
Недействительные объекты, 200
Неиспользуемый параметр функции, 17
О
Обобщенность кода, 94; 142
Объединение, 198
Оператор, 155
++, --, 62
<<, >>, 119
Присваивания, 113
Ошибка, 151; 154
Времени выполнения, 146
Обработка, 159
При работе с STL, 174
П
Память, 125
Перегрузка операторов, 25; 59
&&, || и , , 65
Переносимость, 34
Перлис, Алан, 23; 39; 57; 117; 143; 187
Повторная генерация исключения, 158
Повторное использование, 80
Поиск
Зависящий от аргументов, 118; 120; 136
Имен, 73; 75; 78
Кенига, 118; 120
Полиморфизм, 74; 110; 158; 188; 201
Динамический, 80; 134
Статический, 78; 134
Порядок вычисления аргументов функции, 67
Порядок инициализации, 32
Постконструктор, 102
Постусловие, 148
Потоки выполнения, 34
Правило
Одного определения, 124
Предикат, 182
Предусловие, 148
Преждевременная оптимизация, 25; 29
Преждевременная пессимизация, 31
Преобразование типов, 64; 85
Принцип
Инверсии зависимостей, 54; 77
Интерфейса, 118
Открытости-Закрытости, 188
Подстановки Лисков, 79
Присваивание, 37
Копирующее, 113
Самому себе, 113; 152
Пространство имен, 122
Стр.223
Р
Работа с ресурсами, 37
Разработка библиотеки, 30; 31; 33; 36; 59; 66;130; 136
Распределение памяти, 125; 167
Рецензирование кода, 21
С
Сассман, Джеральд, 25
Сборка программы, 19
Связность, 94
Связывание, 73; 126
Динамическое, 134
Статическое, 135
Сериализация, 32; 34
Система управления версиями, 20
Сложность алгоритма, 27
Совместно используемые данные, 32
Сокрытие данных, 33; 87; 89; 197
Сокрытие имен, 82
Спенсер, Генри, 187
Специализация шаблона функции, 140
Спецификации исключений, 160
Срезка, 76; 110
Статическое связывание, 135
Стиль проектирования, 23
Страуструп, Бьярн, 69; 133; 143; 163;173
Т
Табуляция, 15
Транзакция, 106; 151
У
Указатель на реализацию, 91
Условия гонки, 34
Утечка, 38
Ф
Функциональный объект, 184; 186
Функция, 50
Аргумент по умолчанию, 81
Виртуальная, 83; 102
Встраиваемая, 127
Инвариант, 148
Неиспользуемый параметр, 17
Обмена, 107; 114; 151
Освобождения ресурсов, 107; 151
Перегрузка, 85; 140
Передача параметров по значению, 43; 58
Передача параметров по ссылке, 58
Передача параметров посредством указателей, 58
Перекрытие, 81
Порядок вычисления аргументов, 67
Постусловие, 148
Предусловие, 148
С переменным количеством аргументов, >199
Шаблон, 140
Функция обратного вызова, 36
Ш
Шаблон, 134
Точка настройки, 136
Функции, 140
Шаблон проектирования
Acyclic Visitor, 54
Command, 54; 135
Nonvirtual Interface, 83; 104; 111
Observer, 176
Singleton, 52
Template Method, 83; 104
Visitor, 54; 135
Шварц, Джерри, 127
Э
Эффективность алгоритма, 27; 169
Научно-популярное издание
Герб Саттер, Андрей Александреску
Стандарты программирования на C++
Литературный редактор С.Г. Татаренко
Верстка О.В. Мишутина
Художественный редактор Е/Л. Дынник
Корректор Л.А. Гордиенко
Издательский дом "Вильямс". 101509, Москва, ул. Лесная, д. 43, стр. 1.
Подписано в печать 22.07.2005. Формат 70X100/16.
Гарнитура Times. Печать офсетная.
Усл. печ.л. 19,35. Уч.-изд. л. 13,2.
Тираж 3000 экз. Заказ № 2182.
Отпечатано с диапозитивов в ФГУП "Печатный двор"
Министерства РФ по делам печати,
телерадиовещания и средств массовых коммуникаций.
197110, Санкт-Петербург, Чкаловский пр., 15.
Проектирование классов и наследование
Наиболее важный аспект разработки программного обеспечения — ясно понимать, что именно вы пытаетесь построить.
— Бьярн Страуструп (Bjarne Stroustrup)
Какого вида классы предпочитает разрабатывать и строить ваша команда? Почему?
Интересно, что большинство рекомендаций данного раздела вызваны в первую очередь вопросами зависимостей. Например, наследование— вторая по силе взаимосвязь, которую можно выразить в C++ (первая — отношение дружбы), и такую сильную связь надо использовать очень осторожно и продуманно.
В этом разделе мы сконцентрируем внимание на ключевых вопросах проектирования классов — как сделать это правильно, как не допустить ошибку, избежать ловушек, и в особенности — как управлять зависимостями.
В следующем разделе мы обратимся к Большой Четверке специальных функций — конструктору по умолчанию, копирующему конструктору, копирующему присваиванию и деструктору.
В этом разделе мы считаем самой важной рекомендацию 33 — "Предпочитайте минимальные классы монолитным".
Стр.70
Проектирование классов и наследование
32.Ясно представляйте, какой вид класса вы создаете
Существует большое количество различных видов классов, и следует знать, какой именно класс вы создаете.
Стр.210
33. Предпочитайте минимальные классы монолитным
Разделяй и властвуй: небольшие классы легче писать, тестировать и использовать. Они также применимы в большем количестве ситуаций. Предпочитайте такие небольшие классы, которые воплощают простые концепции, классам, пытающимся реализовать как несколько концепций, так и сложные концепции (см. рекомендации 5 и 6).
34. Предпочитайте композицию наследованию
Избегайте "налога на наследство": наследование— вторая по силе после отношения дружбы взаимосвязь, которую можно выразить в C++. Сильные связи нежелательны, и их следует избегать везде, где только можно. Таким образом, следует предпочитать композицию наследованию, кроме случаев, когда вы точно знаете, что делаете и какие преимущества дает наследование в вашем проекте.
35. Избегайте наследования от классов, которые не спроектированы для этой цели
Классы, предназначенные для автономного использования, подчиняются правилам проектирования, отличным от правил для базовых классов (см. рекомендацию 32). Использование автономных классов в качестве базовых является серьезной ошибкой проектирования и его следует избегать. Для добавления специфического поведения предпочтительно вместо функций-членов добавлять обычные функции (см. рекомендацию 44). Для того чтобы добавить состояние, вместо наследования следует использовать композицию (см. рекомендацию 34). Избегайте наследования от конкретных базовых классов.
36. Предпочитайте предоставление абстрактных интерфейсов
Вы любите абстракционизм? Абстрактные интерфейсы помогают вам сосредоточиться на проблемах правильного абстрагирования, не вдаваясь в детали реализации или управления состояниями. Предпочтительно проектировать иерархии, реализующие абстрактные интерфейсы, которые моделируют абстрактные концепции.
37. Открытое наследование означает заменимость. Наследовать надо
не для повторного использования, а чтобы быть повторно использованным
Открытое наследование позволяет указателю или ссылке на базовый класс в действительности обращаться к объекту некоторого производного класса без изменения существующего кода и нарушения его корректности.
Не применяйте открытое наследование для того, чтобы повторно использовать код (находящийся в базовом классе); открытое наследование необходимо для того, чтобы быть повторно использованным (существующим кодом, который полиморфно использует объекты базового класса).
38. Практикуйте безопасное перекрытие
Ответственно подходите в перекрытию функций. Когда вы перекрываете виртуальную функцию, сохраняйте заменимость; в частности, обратите внимание на пред- и постусловия в базовом классе. Не изменяйте аргументы по умолчанию виртуальных функций. Предпочтительно явно указывать перекрываемые функции как виртуальные. Не забывайте о сокрытии перегруженных функций в базовом классе.
39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными
В базовых классах с высокой стоимостью изменений (в частности, в библиотеках) лучше делать открытые функции невиртуальными. Виртуальные функции лучше делать закрытыми, или защищенными — если производный класс должен иметь возможность вызывать их базовые версии (этот совет не относится к деструкторам; см. рекомендацию 50).
Стр.211
40. Избегайте возможностей неявного преобразования типов
Не все изменения прогрессивны: неявные преобразования зачастую приносят больше вреда, чем пользы. Дважды подумайте перед тем, как предоставить возможность неявного преобразования к типу и из типа, который вы определяете, и предпочитайте полагаться на явные преобразования (используйте конструкторы, объявленные как explicit, и именованные функции преобразования типов).
41. Делайте данные-члены закрытыми (кроме случая агрегатов в стиле структур С)
Данные-члены должны быть закрыты. Только в случае простейших типов в стиле структур языка С, объединяющих в единое целое набор значений, не претендующих на инкапсуляцию и не обеспечивающих поведение, делайте все данные-члены открытыми. Избегайте смешивания открытых и закрытых данных, что практически всегда говорит о бестолковом дизайне.
42. Не допускайте вмешательства во внутренние дела
Избегайте возврата дескрипторов внутренних данных, управляемых вашим классом, чтобы клиенты не могли неконтролируемо изменять состояние вашего объекта, как своего собственного.
43. Разумно пользуйтесь идиомой Pimpl
C++ делает закрытые члены недоступными, но не невидимыми. Там, где это оправдывается получаемыми преимуществами, следует подумать об истинной невидимости, достигаемой применением идиомы Pimpl (указателя на реализацию) для реализации брандмауэров компилятора и повышения сокрытия информации (см. рекомендации Пи 41).
44. Предпочитайте функции, которые не являются ни членами, ни друзьями
Там, где это возможно, предпочтительно делать функции не членами и не друзьями классов.
45. new и delete всегда должны разрабатываться вместе
Каждая перегрузка void* operator new(parms) в классе должна сопровождаться соответствующей перегрузкой оператора void operator delete(void*, parms), где parms — список типов дополнительных параметров (первый из которых всегда std::size_t). To же относится и к операторам для массивов new[] и delete[].
46. При наличии пользовательского new следует предоставлять все стандартные типы этого оператора
Если класс определяет любую перегрузку оператора new, он должен перегрузить все три стандартных типа этого оператора — обычный new, размещающий и не генерирующий исключений. Если этого не сделать, то эти операторы окажутся скрытыми и недоступными пользователям вашего класса.
Пространства имен и модули
Системы имеют подсистемы, которые в свою очередь состоят из подсистем и так до бесконечности — именно поэтому мы всегда движемся сверху вниз.
— Алан Перлис (Alan Perlis)
Пространство имен — очень важный инструмент для управления именами и снижения количества коллизий имен. То же относится и к модулям, которые, помимо этого, представляют собой инструментарий для работы с версиями. Мы определим модуль как отдельный компонент программы, содержащий тесно связанные между собой ее элементы (см. рекомендацию 5) и поддерживаемый одним и тем же программистом или группой; обычно модуль всегда компилируется одним и тем же компилятором с использованием одних и тех же опций. Модули имеются на разных уровнях детализации в широком диапазоне размеров. С одной стороны, модуль может быть минимального размера, представляя собой отдельный объектный файл, содержащий только один класс; с другой стороны, он может быть, например, отдельной динамической библиотекой, генерируемой из множества исходных файлов, содержимое которых образует подсистему внутри приложения большего размера или выпускается отдельно. Модуль может даже представлять собой огромную библиотеку, состоящую из множества небольших модулей (статических или динамических библиотек), содержащих тысячи разных типов. Несмотря на то, что такие библиотеки в стандарте C++ не упоминаются, программисты постоянно создают и используют библиотеки, и хорошо продуманная модуля-ризация является фундаментальной частью успешного управления зависимостями (см., например, рекомендацию 11).
Трудно представить себе программу значительного размера, которая не использует как пространства имен, так и модули. В этом разделе мы рассмотрим основные рекомендации по использованию двух этих взаимосвязанных инструментов, наряду с их взаимодействием с другими частями языка программирования и среды времени выполнения. Эти рекомендации помогут вам наиболее эффективно воспользоваться таким мощным инструментарием и избежать возможных неприятностей.
В этом разделе мы считаем наиболее значимой рекомендацию 58 — "Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы".
Стр.118
Пространства имен и модули
57.Храните типы и их свободный интерфейс в одном пространстве имен
Функции, не являющиеся членами и разработанные как часть интерфейса класса X (в особенности операторы и вспомогательные функции), должны быть определены в том оке пространстве имен, что и X, что обеспечивает их корректный вызов.
58. Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы
Оберегайте ваши типы от непреднамеренного поиска, зависящего от аргументов (argument-dependent lookup — ADL, известный также как поиск Кёнига); однако преднамеренный поиск должен завершаться успешно. Этого можно добиться путем размещения типов в своих собственных пространствах имен (вместе с непосредственно связанными с ними свободными функциями; см. рекомендацию 57). Избегайте помещения типов в те же пространства имен, что и шаблоны функций или операторов).
59. Не используйте using для пространств имен в заголовочных файлах или перед директивой #include
Директива using для пространств имен создана для вашего удобства, а не для головной боли других. Никогда не используйте объявления или директивы using перед директивой #include.
Вывод: не используйте директивы using для пространств имен или using-объявления в заголовочных файлах. Вместо этого полностью квалифицируйте все имена. (Второе правило следует из первого, поскольку заголовочные файлы не могут знать, какие другие директивы #include могут появиться в тексте после них.)
60. Избегайте выделения и освобождения памяти в разных модулях
Золотое правило программиста — положи, где взял. Выделение памяти в одном модуле, а освобождение в другом делает программу более хрупкой, создавая тонкую дальнюю зависимость между этими модулями. Такие модули должны быть компилируемы одной и той же версией компилятора с одними и теми оке флагами (в частности, отладочные версии и версии NDEBUG) и с одной и той же реализацией стандартной библиотеки; кроме того, с практической точки зрения лучше, чтобы модуль, выделяющий память, оставался загружен при ее освобождении.
61. Не определяйте в заголовочном файле объекты со связыванием
Объекты со связыванием, включая переменные или функции уровня пространства имен, обладают выделенной для них памятью. Определение таких объектов в заголовочных файлах приводит либо к ошибкам времени компоновки, либо к бесполезному расходу памяти. Помещайте все объекты со связыванием в файлы реализации.
62. Не позволяйте исключениям пересекать границы модулей
Не бросайте камни в соседский огород — поскольку нет повсеместно распространенного бинарного стандарта для обработки исключений C++, не позволяйте исключениям пересекать распространяться между двумя частями кода, если только вы не в состоянии контролировать, каким компилятором и с какими опциями скомпилированы обе эти части кода. В противном случае может оказаться, что модули не поддерживают совместимые реализации распространения исключений. Обычно это правило сводится к следующему: не позволяйте исключениям пересекать границы модулей/подсистем.
63. Используйте достаточно переносимые типы в интерфейсах модулей
Не позволяйте типам появляться во внешнем интерфейсе модуля, если только вы не уверены в том, что все пользователи смогут корректно их понять и работать с ними. Используйте наивысший уровень абстракции, который в состоянии понять клиентский код.
Стр.214
Резюме из резюмеВопросы организации и стратегии
0.Не мелочитесь, или Что не следует стандартизировать
Скажем кратко: не мелочитесь.
1. Компилируйте без замечаний при максимальном уровне предупреждений
Следует серьезно относиться к предупреждениям компилятора и использовать максимальный уровень вывода предупреждений вашим компилятором. Компиляция должна выполняться без каких-либо предупреждений. Вы должны понимать все выдаваемые предупреждения и устранять их путем изменения кода, а не снижения уровня вывода предупреждений.
2. Используйте автоматические системы сборки программ
Нажимайте на одну (единственную) кнопку: используйте полностью автоматизированные ("в одно действие") системы, которые собирают весь проект без вмешательства пользователя.
3. Используйте систему контроля версий
Как гласит китайская пословица, плохие чернила лучше хорошей памяти: используйте системы управления версиями. Не оставляйте файлы без присмотра на долгий срок. Проверяйте их всякий раз после того, как обновленные модули проходят тесты на работоспособность. Убедитесь, что внесенные обновления не препятствуют корректной сборке программы.
4. Одна голова хорошо, а две — лучше
Регулярно просматривайте код всей командой. Чем больше глаз — тем выше качество кода. Покажите ваш код другим и познакомьтесь с их кодом — это принесет пользу всем.
Шаблоны и обобщенность
Место для вашей цитаты.
— Бьярн Страуструп (Bjarne Stroustrup),
[Stroustrup00] §13
Аналогично: место для вашего введения.
В этом разделе мы считаем наиболее значимой рекомендацию 64 — "Разумно сочетайте статический и динамический полиморфизм".
Стр.134
Шаблоны и обобщенность
64. Разумно сочетайте статический и динамический полиморфизм
Статический и динамический полиморфизм дополняют друг друга. Следует ясно представлять себе их преимущества и недостатки, чтобы использовать каждый из них там, где он дает наилучшие результаты, и сочетать их так, чтобы получить лучшее из обоих миров.
65. Выполняйте настройку явно и преднамеренно
При разработке шаблона точки настройки должны быть написаны корректно, с особой тщательностью, а также ясно прокомментированы. При использовании шаблона необходимо четко знать, как именно следует настроить шаблон для работы с вашим типом, и выполнить соответствующие действия.
66. Не специализируйте шаблоны функций
При расширении некоторого шаблона функции (включая std::swap) избегайте попыток специализации шаблона. Вместо этого используйте перегрузку шаблона функции, которую следует поместить в пространство имен типа(ов), для которых разработана данная перегрузка (см. рекомендацию 57). При написании собственного шаблона функции также избегайте его специализации.
67. Пишите максимально обобщенный код
Используйте для реализации функциональности наиболее обобщенные и абстрактные средства.
Список литературы
Примечание: для удобства читателей весь список литературы доступен по адресу http://www.gotw.ca/publications/c++cs/bibliography.htm
Ссылки, выделенные подчеркиванием (например, [Abrahams96] ), представляют собой гиперссылки в приведенной выше странице.
[Abelson96] | Abelson H. and Sussman G. J. Structure and Interpretation of Computer Programs (2nd Edition) (MIT Press, 1996). | ||
[Abrahams96] | Abrahams D. Exception Safety in STLport. STLport website, 1996. | ||
[Abrahams01a] | Abrahams D. Exception Safety in Generic Components, in Jazayeri M., Loos R., Musser D. (eds.), Generic Programming: International Seminar on Generic Programming, Dagstuhl Castle, Germany, April/May 1998, Selected Papers, Lecture Notes in Computer Science 1766 (Springer, 2001). | ||
[Abrahams01b] | Abrahams D. Error and Exception Handling. [Boost] website, 2001. | ||
[Alexandrescu00a] | Alexandrescu A. Traits: The else-if-then of Types. C++ Report, 12(4), April 2000. | ||
[Alexandrescu00b] | Alexandrescu A. Traits on Steroids. C++ Report, 12(6), June 2000. | ||
[Alexandrescu00c] | Alexandrescu A. and Marginean P. Change the Way You Write Exception-Safe Code—Forever. C/C++ Users Journal, 18(12), December 2000. | ||
[Alexandrescu01] | Alexandrescu A. Modern C++ Design. Addison-Wesley, 2001.
Перевод: Александреску А. Современное проектирование на C++. Серия C+ + In-Depth, т.З. — М.: Издательский дом "Вильямс", 2002. | ||
[Alexandrescu01a] | Alexandrescu A. A Policy-Based basic_string Implementation. C/C++ Users Journal, 19(6), June 2001. | ||
[Alexandrescu02a] | Alexandrescu A. Multithreading and the C++ Type System. InformJT website, February 2002. | ||
[Alexandrescu02b] | Alexandrescu A. "Discriminated Unions (I)," "... (II)," and "... (III)". C/C++ Users Journal, 20(4,6,8), April/June/August 2002. | ||
[Alexandrescu03a] | Alexandrescu A. Move Constructors. C/C++ Users Journal, 21(2), February 2003. | ||
[Alexandrescu03b] | Alexandrescu A. Assertions. C/C++ Users Journal, 21(4), April 2003. | ||
[Alexandrescu03c] | Alexandrescu A. and Marginean P. Enforcements. C/C++ Users Journal, 21(6), June 2003. | ||
[Alexandrescu03d] | Alexandrescu A. and Held D. Smart Pointers Reloaded. C/C++ Users Journal, 21(10), October 2003. | ||
[Alexandrescu04] | Alexandrescu A. Lock-Free Data Structures. C/C++ Users Journal, 22(10), October 2004. | ||
[Allison98] | Allison С. С & C++ Code Capsules. Prentice Hall, 1998. | ||
[Austern99] | Austern M. H. Generic Programming and the STL. Addison-Wesley, 1999. | ||
[Barton94] | Barton J. and Nackman L. Scientific and Engineering C++. Addison-Wesley, 1994. | ||
[Bentley00] | Bentley J. Programming Pearls (2nd Edition). Addison-Wesley, 2000. Перевод: Бентли Дж. Жемчужины программирования. Второе издание. — СПб.: Питер, 2002. | ||
[BetterSCM] | Web-узел Better SCM Initiative. | ||
[Boost] | C++ Boost. | ||
[BoostLRG] | Boost Library Requirements and Guidelines. (Web-узел Boost). | ||
[Brooks95] | Brooks F. The Mythical Man-Month. Addison-Wesley, 1975; reprinted with corrections in 1995. | ||
[Butenhof97] | Butenhof D. Programming with POSIX Threads. Addison-Wesley, 1997. | ||
[Cargill92] | Cargill T. C++ Programming Style. Addison-Wesley, 1992. |
Стр.203
[С90] |
ISO/IEC 9899:1990(E), Programming Languages — С ( ISO C90 and ANSI C89 Standard). |
[C99] |
ISO/IEC 9899:1999(E), Programming Languages — С (revised ISO and ANSI C99 Standard). |
[С++98] |
ISO/IEC 14882:1998(E), Programming Languages—C++ (ISO and ANSI C++ Standard). |
[С++03] |
ISO/IEC 14882:2003(E), Programming Languages—C++ (updated ISO and ANSI C++ Standard including the contents of [C++98] plus errata corrections). |
[C++TR104] |
ISO/EEC JTC1/SC22/WG21/N1711. (Draft) Technical Report on Standard Library Extensions (ISO C++ committee working document, November 2004). Близкий к окончанию черновик с расширениями стандартной библиотеки C++, включая shared_ptr. |
[Cline99] |
Cline M., Lomow G., and Girou M. C++ FAQs (2ndEdition). Addison-Wesley, 1999. |
[Constantine95] |
Constantine L. Constantine on Peopleware. Yourdon Press, 1995. |
[Coplien92] |
Coplien J. Advanced C++ Programming Styles and Idioms. Addison-Wesley, 1992. |
[Cormen01] |
Cormen Т., Leiserson C, Rivest R., Stein C. Introduction to Algorithms (2nd Edition). MIT Press, 2001. |
Перевод: Кормен Т., Лейзерсон Ч., Ривест Р., Стейн К. Введение в алгоритмы. 2-е изд. — М.: Издательский дом "Вильямс" (в печати) |
|
[CVS] |
Web-узел CVS. |
[Cowan01] |
Cowan С, Barringer ML, Beattie S., and Kroah-Hartman G. FormatGuard: Automatic Protection From printf Format String Vulnerabilities. Proceedings of the 2001 VSENIX Security Symposium, August 2001, Washington, D.C. |
[Dewhurst03] |
Dewhurst S. C++ Gotchas. Addison-Wesley, 2003. |
[Dinkumware-Safe] |
Dinkumware Unabridged Library documentation (Web-узел Dinkumware). |
[Ellis90] |
Ellis M. and Stroustrup B. The Annotated C++ Reference Manual. Addison-Wesley, 1990. |
Перевод: Эллис М., Страуструп Б. Справочное руководство по языку программирования C++ с комментариями. — М.: Мир, 1992. |
|
[Gamma95] |
Gamma E., Helm R., Johnson R., and Vlissides J. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. |
Перевод: Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. — СПб.: Питер, 2001. |
|
[GnuMake] |
Gnu make (Web-узел Gnu). |
[GotW] |
Sutter H. Guru of the Week column. |
[Henney00] |
Henney K. C++ Patterns: Executing Around Sequences (EuroPLoP 2000 proceedings). |
[Henney01] |
Henney K. C++ Patterns: Reference Accounting (EuroPLoP 2001 proceedings). |
[Henney02a] |
Henney K. Stringing Things Along. Application Development Advisor, July-August 2002. |
[Henney02b] |
Henney K. The Next Best String. Application Development Advisor, October 2002. |
[Henricson97] |
Henricson M. and Nyquist E. Industrial Strength C++. Prentice Hall,1997. |
[Horstmann95] |
Horstmann С S. Safe STL., 1995. |
[Josuttis99] |
Josuttis N. The C++ Standard Library. Addison-Wesley, 1999. |
Перевод: Джосьютис Н. C++. Стандартная библиотека. — СПб.: Питер (в печати). |
|
[Keffer95] |
Keffer Т. Rogue Wave C++ Design, Implementation, and Style Guide. Rogue Wave Software, 1995. |
[Kernighan99] |
Kernighan B. and Pike R. The Practice of Programming. Addison-Wesley, 1999. |
[Knuth89] |
Knuth D. The Errors of TeX. Software—Practice & Experience, 19(7), July 1989. |
[Knuth97a] |
Knuth D. The Art of Computer Programming, Volume 1: Fundamental Algorithms, 3rd Edition. Addison-Wesley, 1997. |
Перевод: Кнут Д. Искусство программирования, том I. Основные алгоритмы, 3-е изд. — М.: Издательский дом "Вильямс", 2000. |
Стр.204
[Knuth97b] |
Knuth D. The Art of Computer Programming, Volume 2: Seminumerical Algorithms, 3rd Edition. Addison-Wesley, 1997. |
Перевод: Кнут Д. Искусство программирования, том 1. Получисленные алгоритмы, 3-е изд. — М.: Издательский дом "Вильямс", 2000. |
|
[Knuth98] |
Knuth D. The Art of Computer Programming, Volume 3: Sorting and Searching, 2nd Edition. Addison-Wesley, 1998. |
Перевод: Кнут Д. Искусство программирования, том 1. Сортировка и поиск, 2-е изд. — М.: Издательский дом "Вильямс", 2000. |
|
[Koenig97] |
Koenig A. and Moo В. Ruminations on C++. Addison-Wesley, 1997. |
[Lakos96] |
Lakos J. Large-Scale C++ Software Design. Addison-Wesley, 1996. |
[Liskov88] |
Liskov B. Data Abstraction and Hierarchy. SIGPLAN Notices, 23(5), May 1988. |
[Martin96a] |
Martin R. C. The Dependency Inversion Principle. C++ Report, 8(5), May 1996. |
[Martin96b] |
Martin R. С Granularity. C++ Report, 8(9), October 1996. |
[Martin96c] |
Martin R. С The Open-Closed Principle. C++ Report, 8(1), January 1996. |
[Martin98] |
Martin R. C, Riehle D., Buschmann F. (eds.). Pattern Languages of Program Design 3. Addison-Wesley, 1998. |
[Martin00] |
Martin R. C. Abstract Classes and Pure Virtual Functions in Martin R. C. (ed.), More C++ Gems. Cambridge University Press, 2000. |
[McConnell93] |
McConnell S. Code Complete. Microsoft Press, 1993. |
[Metrowerks] |
Metrowerks. |
[Meyer00] |
Meyer B. Object-Oriented Software Construction, 2nd Edition. Prentice Hall, 2000. |
[Meyers96] |
Meyers S. More Effective C++. Addison-Wesley, 1996. |
Перевод: Мейерс С. Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению ваших программ и проектов. — М.: ДМК Пресс, 2000. |
|
[Meyers97] |
Meyers S. Effective C++, 2nd Edition. Addison-Wesley, 1997. |
Перевод: Мейерс С. Эффективное использование C++. 50 рекомендаций по улучшению ваших программ и проектов. — М.: ДМК Пресс, 2000. |
|
[Meyers00] |
Meyers S. How Non-Member Functions Improve Encapsulation. C/C++ Users Journal, 18(2), February 2000. |
[Meyers01] |
Meyers S. Effective STL Addison-Wesley, 2001. |
Перевод: Мейерс С. Эффективное использование STL. — СПб.: Питер, 2002. |
|
[Meyers04] |
Meyers S. and Alexandrescu A. C++ and the Perils of Double-Checked Locking, Part 1 and ...Part 2. Dr. Dobb's Journal, 29(7,8), July and August 2004. |
[Milewski01] |
Milewski B. C++ In Action. Addison-Wesley, 2001. |
[Miller56] |
Miller G. A. The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information. The Psychological Review, 1956, vol. 63. |
[MozillaCRFAQ] |
Frequently Asked Questions About mozilla.org 's Code Review Process (Web-узел Mozilla). |
[Murray93] |
Murray R. C++ Strategies and Tactics. Addison-Wesley, 1993. |
[Musser01] |
Musser D. R., Derge G. J., and Saini A. STL Tutorial and Reference Guide, 2nd Edition. Addison-Wesley, 2001. |
[Parnas02] |
Parnas D. The Secret History of Information Hiding. Software Pioneers: Contributions To Software Engineering, Springer-Verlag, New York, 2002. |
[Peters99] |
Peters T. The Zen of Python. Сотр.lang.python, June 1999. |
[Piwowarski82] |
Piwowarski P. A Nesting Level Complexity Measure. ACM SIGPLAN Notices, 9/1982. |
[Saks99] |
Saks D. Thinking Deeply, Thinking Deeper, and Thinking Even Deeper. C/C++ Users Journal, 17(4,5,6), April, May, and June 1999. |
[Schmidt01] |
Schmidt D., Stal M., Rohnert H., Buschmann F. Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects, Wiley, 2001. |
Стр.205
[SeamonkeyCR] |
Seamonkey Code Reviewer's Guide (Web-узел Mozilla). |
[Sedgewick98] |
Sedgewick R. Algorithms in C++, Parts 1-4: Fundamentals, Data Structure, Sorting, Searching, 3rd Edition. Addison-Wesley, 1998. |
[STLport-Debug] |
Fomitchev B. STLport: Debug Mode (Web-узел STLport). |
[Stroustrup94] |
Stroustrup B. The Design and Evolution of C++. Addison-Wesley, 1994. |
Перевод: Страуструп Б. Дизайн и эволюция языка C++. — М.: ДМК Пресс, 2000. |
|
[Stroustrup00] |
Stroustrup В. The C++ Programming Language (Special 3rd Edition). Addison-Wesley, 2000. |
Перевод: Страуструп Б. Язык программирования C++. Специальное издание. — М.: Бином, 2001. |
|
[Sutter99] |
Sutter H. ACID Programming. Guru of the Week #6]. |
[Sutter00] |
Sutter H. Exceptional C++. Addison-Wesley, 2000. |
Перевод: Саттер Г. Решение сложных задач на C++. Серия C++ In-Depth, т.4. — М.: Издательский дом "Вильямс", 2002. |
|
[Sutter02] |
Sutter H. More Exceptional C++. Addison-Wesley, 2002. |
Перевод: Саттер Г. Решение сложных задач на C++. Серия C++ In-Depth, т.4. — М.: Издательский дом "Вильямс", 2002. |
|
[Sutter03] |
Sutter H. Generalizing Observer. C/C++ Users Journal, 21(9), September 2003. |
[Sutter04] |
Sutter H. Exceptional C++ Style. Addison-Wesley, 2004. |
Перевод: Саттер Г. Новые сложные задачи на C++. — М.: Издательский дом "Вильямс" (в печати). |
|
[Sutter04a] |
Sutter H. Function Types. C/C++ Users Journal, 22(7), July 2004. |
[Sutter04b] |
Sutter H. When and How To Use Exceptions. C/C++ Users Journal, 22(8), August 2004. |
[Sutter04c] |
Sutter H. Just Enough' Thread Safety. C/C++ Users Journal, 22(9), September 2004. |
[Sutter04d] |
Sutter H. How to Provide (or Avoid) Points of Customiza-tion in Templates. C/C+ + Users Journal, 22(11), November 2004. |
[SuttHysl01] |
Sutter H. and Hyslop J. Hungarian wartHogs. C/C++ Users Journal, 19(11), November 2001. |
[SuttHysl02] |
Sutter H. and Hyslop J. A Midsummer Night's Madness. C/C++ Users Journal, 20(8), August 2002. |
[SuttHysl03] |
Sutter H. and Hyslop J. Sharing Causes Contention. C/C++ Users Journal, 21(4), April 2003. |
[SuttHysl04a] |
Sutter H. and Hyslop J. Getting Abstractions. C/C++ Users Journal, 22(6), June 2004. |
[SuttHysl04b] |
Sutter H. and Hyslop J. Collecting Shared Objects. C/C++ Users Journal, 22(8), August 2004. |
[Taligent94] |
Taligent's Guide to Designing Programs. Addison-Wesley, 1994. |
[Tsai01] |
Tsai T. and Singh N. Libsafe 2.0: Detection of Format String Vulnerability Exploits. Avaya Labs, March 2001. |
[Vandevoorde03] |
Vandevoorde D. and Josuttis N. C++ Templates. Addison-Wesley, 2003. |
Перевод: Вандевурд Д., Джосаттис Н. Шаблоны C++. Справочник разработчика. — М.: Издательский дом "Вильямс", 2003. |
|
[Webber03] |
Webber А. В. Modern Programming Languages: A Practical Introduction. Franklin, Beedle & Associates, 2003. |
Стандарты кодирования и вы
Хорошие стандарты кодирования могут принести немалую выгоду с различных точек зрения.
Повышение качества кода. Работа в соответствии со стандартом приводит к однотипному решению одинаковых задач, что повышает ясность кода и упрощает его сопровождение.
Повышение скорости разработки. Разработчику не приходится решать все задачи и принимать решения "с нуля".
Повышение уровня взаимодействия в команде. Наличие стандарта позволяет уменьшить разногласия в команде и устранить ненужные дебаты по мелким вопросам, облегчает понимание и поддержку чужого кода членами команды.
Согласованность в работе. При использовании стандарта разработчики направляют свои усилия в верном направлении, на решение действительно важных задач.
В напряженной обстановке, при жестких временных рамках люди обычно делают то, чему их учили, к чему они привыкли. Вот почему в больницах в пунктах первой помощи предпочитают опытных, тренированных сотрудников — даже хорошо обученные и знающие новички склонны к панике.
У разработчиков программного обеспечения регулярно возникают ситуации, когда что-то надо было сделать еще вчера — на позавчера. Когда на нас давит график работ (который к тому же имеет тенденцию сдвигаться в одном направлении, и то, что по плану должно было заработать завтра, от нас начинают требовать еще вчера...), мы работаем так, как приучены. Неряшливые программисты, которые даже при обычной неспешной работе не помнят о правильных принципах разработки программного обеспечения (а то и вовсе не знакомы с ними), при нехватке времени окажутся еще небрежнее, а их код будет изобиловать ошибками. Соответственно, программист, который выработал в себе хорошие привычки и регулярно ими пользуется, при "повышенном давлении" будет продолжать выдавать качественный код.
Стандарты кодирования, приведенные в этой книге, представляют собой набор рекомендаций по написанию высококачественного кода на C++. В них сконцентрирован богатый коллективный опыт всего сообщества программистов на C++. Многие из этих знаний разбросаны по частям по самым разным книгам, но не меньшее количество знаний передается изустно. Мы постарались собрать разрозненные сведения в одной книге в виде коллекции ясных, компактных правил с пояснениями, простых для понимания и следования им.
Конечно, даже самые лучшие стандарты не могут помешать написанию плохого кода. То же можно сказать о любом языке программирования, процессе или методологии. Хороший набор стандартов воспитывает хорошие привычки и дисциплину, превышающую обычные
Стр.11
нормы. Это служит хорошим фундаментом для дальнейшего усовершенствования и повышения квалификации. Это не преувеличение и не красивые слова — перед тем, как начать писать стихи, надо владеть словарным запасом и знать грамматику. Мы надеемся, что наша книга упростит для вас путь к поэзии программирования.
Эта книга предназначена для программистов всех уровней.
Если вы начинающий программист — мы надеемся, что рекомендации и их пояснения достаточно поучительны и помогут вам в понимании того, какие стили и идиомы C++ поддерживает наиболее естественным образом. В описании каждого правила и рекомендации приводится краткое обоснование и обсуждение, чтобы вы не просто механически запомнили правило, а поняли его суть.
Для программистов среднего и высокого уровня при описании каждого правила приводится список ссылок, который позволит вам углубленно изучить заинтересовавший вас вопрос, проведя поиск корней правила в системе типов, грамматике и объектной модели C++.
Каким бы ни был ваш уровень как программиста — вероятно, вы работаете над сложным проектом не в одиночку, а в команде. Именно в этом случае разработка стандартов кодирования окупается сполна. Вы можете использовать их для того, чтобы подтянуть всю свою команду к одному, более высокому уровню, и обеспечить повышение качества разрабатываемого кода.
Стандарты программирования на C++101 правило и рекомендация
Герб Саттер, Андрей Александреску
Издательский дом "Вильямс"
Москва • Санкт-Петербург • Киев
2005
ББК 32.973.26-018.2.75
С21
УДК 681.3.07
Издательский дом "Вильямс"
Зав. редакцией С.Н. Тригуб
Перевод с английского и редакция канд. техн. наук И.В. Красикова
По общим вопросам обращайтесь в Издательский дом "Вильямс" по адресу:
info@williamspublishing.com, http://www.williamspublishing.com
115419, Москва, а/я 783; 03150, Киев, а/я 152
Саттер, Герб, Александреску, Андрей.
Стандарты программирования на C++.: Пер. с англ. — М.: Издательский дом "Вильямс", 2005.— 224 с. :ил.— Парал. тит. англ.
ISBN 5-8459-0859-0 (рус.)
Эта книга поможет новичку стать профессионалом, так как в ней представлен сконцентрированный лучший опыт программистов на C++, обобщенный двумя экспертами мирового класса.
Начинающий программист найдет в ней простые и понятные рекомендации для ежедневного использования, подкрепленные примерами их конкретного применения на практике.
Опытные программисты найдут в ней советы и новые рекомендации, которые можно сразу же принять на вооружение. Программисты-профессионалы могут использовать эту книгу как основу для разработки собственных стандартов кодирования, как для себя лично, так и для группы, которой они руководят.
Конечно, книга рассчитана в первую очередь на профессиональных программистов с глубокими знаниями языка, однако она будет полезна любому, кто захочет углубить свои знания в данной области.
ББК 32.973.26-018.2.75
Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Addison-Wesley Publishing Company. Inc.
Authorized translation from the English language edition published by Prentice Hall, Copyright © 2005
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher.
Russian language edition was published by Williams Publishing House according to the Agreement with R&l Enterprises International, Copyright © 2005
© Издательский дом "Вильямс", 2005
© Pearson Education, Inc., 2005
ISBN 5-8459-0859-0 (рус.)
ISBN 0-321-11358-6 (англ.)
Стр.5
Стиль кодирования
Константа для одного является переменной для другого.
— Алан Перлис (Alan Perlis)
В этом разделе мы перейдем от вопросов проектирования к вопросам, которые появляются в основном при реальном кодировании.
Правила и рекомендации из этого раздела применимы безотносительно к конкретной области языка программирования (например, функциям, классам или пространствам имен), но приводят к повышению качества вашего кода. Многие из представленных идиом позволяют вашему компилятору активнее помогать вам в работе, а вам — избежать опасных мест (включая неопределенное поведение), которые компилятор не всегда в состоянии выявить. Все это делает ваш код более надежным.
В этом разделе мы считаем наиболее важной рекомендацию 14: "Предпочитайте ошибки компиляции и компоновки ошибкам времени выполнения".
Стр.40
Стиль кодирования
14.Предпочитайте ошибки компиляции и компоновки ошибкам времени выполнения
Не стоит откладывать до выполнения программы выявление ошибок, которые можно обнаружить при ее сборке. Предпочтительно писать код, который использует компилятор для проверки инвариантов в процессе компиляции, вместо того, чтобы проверять их во время работы программы. Проверки времени выполнения зависят от выполнимого кода и данных, так что вы только изредка можете полностью полагаться на них. Проверки времени компиляции, напротив, не зависят от данных и предыстории исполнения, что обычно обеспечивает более высокую степень надежности.
15. Активно используйте const
const — ваш друг: неизменяемые значения проще понимать, отслеживать и мотивировать, т.е. там, где это целесообразно, лучше использовать константы вместо переменных. Сделайте const описанием по умолчанию при определении значения — это безопасно, проверяемо во время компиляции (см. рекомендацию 14) и интегрируемо с системой типов
Стр.208
C++. Не выполняйте преобразований типов с отбрасыванием const кроме как при вызове некорректной с точки зрения употребления const функции (см. рекомендацию 94).
16. Избегайте макросов
Макрос — самый неприятный инструмент С и C++, оборотень, скрывающийся под личиной функции, кот, гуляющий сам по себе и не обращающий никакого внимания на границы ваших областей видимости. Берегитесь его!
17. Избегайте магических чисел
Избегайте использования в коде литеральных констант наподобие 42 или 3.1415926. Такие константы не самоочевидны и усложняют сопровождение кода, поскольку вносят в него трудноопределимый вид дублирования. Используйте вместо них символьные имена и выражения наподобие width*aspectRatio.
18. Объявляйте переменные как можно локальнее
Избегайте "раздувания" областей видимости. Переменных должно быть как можно меньше, а время их жизни — как можно короче. Эта рекомендация по сути является частным случаем рекомендации 10.
19. Всегда инициализируйте переменные
Неинициализированные переменные—распространенный источник ошибок в программах на С и C++. Избегайте их, выработав привычку очищать память перед ее использованием; инициализируйте переменные при их определении.
20. Избегайте длинных функций и глубокой вложенности
Краткость — сестра таланта. Чересчур длинные функции и чрезмерно вложенные блоки кода зачастую препятствуют реализации принципа "одна функция — одна задача" (см. рекомендацию 5), и обычно эта проблема решается лучшим разделением задачи на отдельные части.
21. Избегайте зависимостей инициализаций между единицами компиляции
Объекты уровня пространств имен в разных единицах компиляции не должны зависеть друг от друга при инициализации, поскольку порядок их инициализации не определен. В противном случае вам обеспечена головная боль при попытках разобраться со сбоями в работе программы после внесения небольших изменений в ваш проект и невозможностью его переноса даже на новую версию того же самого компилятора.
22. Минимизируйте зависимости определений и избегайте циклических зависимостей
Избегайте излишних зависимостей. Не включайте при помощи директивы #include определения там, где достаточно предварительного объявления.
Избегайте взаимозависимостей. Циклические зависимости возникают, когда два модуля непосредственно или опосредованно зависят друг от друга. Модуль представляет собой обособленную единицу; взаимозависимые модули не являются полностью отдельными модулями, будучи по сути частями одного большего модуля. Таким образом, циклические зависимости являются противниками модульности и представляют угрозу большим проектам. Избегайте их.
23. Делайте заголовочные файлы самодостаточными
Убедитесь, что каждый написанный вами заголовочный файл компилируем самостоятельно, т.е. что он включает все заголовочные файлы, от которых зависит его содержимое.
24. Используйте только внутреннюю, но не внешнюю защиту директивы #include
Предотвращайте непреднамеренное множественное включение ваших заголовочных файлов директивой #include, используя в них защиту с уникальными именами.
Стр.209
Стиль проектирования
Дураки игнорируют сложности. Прагматики терпят их. Некоторые ухитряются их избегать. И только гении устраняют их.
— Алан Перлис (Alan Perlis)
Я также знал, но забыл афоризм Хоара о том, что преждевременная оптимизация— корень всех зол в программировании.
— Дональд Кнут (Donald Knuth),
The Errors of TeX
[Knuth89]
Очень сложно отделить стиль проектирования от стиля кодирования. Мы попытались поместить в очередном разделе те вопросы, которые обычно проявляются, когда вы начинаете писать реальный код.
В этом разделе внимание уделяется принципам и практическим вопросам, область применения которых больше, чем просто отдельный класс или функция. Классические примеры — баланс между простотой и ясностью (рекомендация 6), избежание преждевременной оптимизации (рекомендация 8) и пессимизации (рекомендация 9). Эти рекомендации применимы не только на уровне кодирования отдельной функции, но и к большим областям — проектированию классов и модулей или к решениям с далеко идущими последствиями по поводу архитектуры приложений. (Они применимы также для всех программистов. Если вы считаете иначе — еще раз прочтите приведенную выше цитату Кнута и даже заучите ее на память.)
Многие из прочих рекомендаций этого и следующего разделов имеют дело с управлением зависимостями — краеугольным камнем проектирования программного обеспечения и часто повторяющейся в данной книге темой. Остановитесь и задумайтесь над произвольной методикой проектирования программного обеспечения — любой хорошей методикой. Какую бы методику вы ни выбрали, так или иначе вы столкнетесь с ослаблением зависимостей. Наследование? Разрабатываемый код должен делать производный класс как можно менее зависимым от базового класса. Минимизация использования глобальных переменных? Снижает дальнодействующие зависимости, осуществляемые посредством широко видимых разным частям программы данных. Абстракция? Устраняет зависимости между кодом, который управляет концепциями, и кодом, который их реализует. Сокрытие информации? Делает код клиента менее зависимым от деталей реализации. Забота об управлении зависимостями отражается в устранении совместного использования состояния (рекомендация 10), применении сокрытия информации (рекомендация 11) и прочем.
В этом разделе самой важной нам кажется рекомендация 6: " Главное — корректность, простота и ясность".
Стр.24
Стиль проектирования
5. Один объект — одна задача
Концентрируйтесь одновременно только на одной проблеме. Каждый объект (переменная, класс, функция, пространство имен, модуль, библиотека) должны решать одну точно поставленную задачу. С ростом объектов, естественно, увеличивается область их ответственности, но они не должны отклоняться от своего предназначения.
6. Главное — корректность, простота и ясность
Корректность лучше быстроты. Простота лучше сложности. Ясность лучше хитроумия. Безопасность лучше ненадежности (см. рекомендации 83 и 99).
7. Кодирование с учетом масштабируемости
Всегда помните о возможном росте данных. Подумайте об асимптотической сложности без преждевременной оптимизации. Алгоритмы, которые работают с пользовательскими данными, должны иметь предсказуемое и, желательно, не хуже чем линейно зависящее от количества обрабатываемых данных время работы. Когда становится важной и необходимой оптимизация, в особенности из-за роста объемов данных, в первую очередь следует улучшать О-сложность алгоритма, а не заниматься микрооптимизациями типа экономии на одном сложении.
Стр.207
8. Не оптимизируйте преждевременно
Как гласит пословица, не подгоняйте скачущую лошадь. Преждевременная оптимизация непродуктивна и быстро входит в привычку. Первое правило оптимизации: не оптимизируйте. Второе правило оптимизации (только для экспертов): не оптимизируйте ни в коем случае. Семь раз отмерь, один раз оптимизируй.
9. Не пессимизируйте преждевременно
То, что просто для вас, — просто и для кода. При прочих равных условиях, в особенности — сложности и удобочитаемости кода, ряд эффективных шаблонов проектирования и идиом кодирования должны естественным образом "стекать с кончиков ваших пальцев" и быть не сложнее в написании, чем их пессимизированные альтернативы. Это не преждевременная оптимизация, а избежание излишней пессимизации.
10. Минимизируйте глобальные и совместно используемые данные
Совместное использование вызывает споры и раздоры. Избегайте совместного использования данных, в особенности глобальных данных. Совместно используемые данные усиливают связность, что приводит к снижению сопровождаемости, а зачастую и производительности.
11. Сокрытие информации
Не выпускайте внутреннюю информацию за пределы объекта, обеспечивающего абстракцию.
12. Кодирование параллельных вычислений
Если ваше приложение использует несколько потоков или процессов, следует минимизировать количество совместно используемых объектов, где это только можно (см. рекомендацию 10), и аккуратно работать с оставшимися.
13. Ресурсы должны быть во владении объектов
Не работайте вручную, если у вас есть мощные инструменты. Идиома C++ "выделение ресурса есть инициализация" (resource acquisition is initialization — RAII) представляет собой мощный инструмент для корректной работы с ресурсами. RAII позволяет компилятору автоматически обеспечить строгую гарантию того, что в других языках надо делать вручную. При выделении ресурса передайте его объекту-владельцу. Никогда не выделяйте несколько ресурсов в одной инструкции.
STL: алгоритмы
Предпочитайте алгоритмы циклам.
— Бьярн Страуструп (Bjarne Stroustrup),
[Stroustrup00] §18.12
Алгоритмы представляют собой циклы — только они лучше циклов. Алгоритмы — это "шаблоны" циклов, с добавлением дополнительной семантики по сравнению с простыми for и do. Конечно, начав использовать алгоритмы, вы начнете использовать и функциональные объекты и предикаты; корректно пишите их и широко используйте в своих программах.
В этом разделе мы считаем наиболее значимой рекомендацию 83 — "Используйте отладочную реализацию STL".
Стр.174
STL: алгоритмы
83.Используйте отладочную реализацию STL
Безопасность превыше всего (см. рекомендацию 6). Используйте отладочную реализацию STL, даже если она имеется только для одного из ваших компиляторов, и даже если она используется только для отладочного тестирования.
84. Предпочитайте вызовы алгоритмов самостоятельно разрабатываемым циклам
Разумно используйте функциональные объекты. В очень простых случаях написанные самостоятельно циклы могут оказаться более простым и эффективным решением. Тем не менее, вызов алгоритма вместо самостоятельно разработанного цикла может оказаться более выразительным, легче сопровождаемым, менее подверженным ошибкам и не менее эффективным.
При вызове алгоритма подумайте о написании собственного функционального объекта, который инкапсулирует всю необходимую логику. Избегайте объединения связывателей параметров и простых функциональных объектов (например, bind2nd, plus), что обычно снижает ясность кода. Подумайте об использовании лямбда-библиотеки [Boost] , которая автоматизирует задачу написания функциональных объектов.
85. Пользуйтесь правильным алгоритмом поиска
Данная рекомендация применима к поиску определенного значения в диапазоне. При поиске в неотсортированном диапазоне используйте алгоритмы find/find_if или count/count_if. Для поиска в отсортированном диапазоне выберите lower_bound, upper_bound, equal_range или (реже) binary_search. (Вопреки своему имени, binary_search обычно — неверный выбор.)
86. Пользуйтесь правильным алгоритмом сортировки
При сортировке вы должны четко понимать, как работает каждый из сортирующих алгоритмов, и использовать наиболее дешевый среди тех, которые пригодны для решения вашей задачи.
87. Делайте предикаты чистыми функциями
Предикат представляет собой функциональный объект, который возвращает ответ да/ нет, обычно в виде значения типа bool. Функция является "чистой" в математическом смысле, если ее результат зависит только от ее аргументов (обратите внимание — в данном случае термин "чистая" не имеет никакого отношения к чисто виртуальным функциям).
Не позволяйте предикатам сохранять или обращаться к состоянию так, чтобы это могло влиять на результат работы оператора operator(); при этом понятие состояния включает как данные-члены, так и глобальные состояния. Для предикатов желательно делать оператор operator() константной функцией-членом (см. рекомендацию 15).
88. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
Предпочтительно передавать алгоритмам функциональные объекты, а не функции, а компараторы ассоциативных контейнеров просто должны быть функциональными объектами. Функциональные объекты адаптируемы и, вопреки ожиданиям, обычно дают более быстрый по сравнению с функциями код.
89. Корректно пишите функциональные объекты
Разрабатывайте функциональные объекты так, чтобы их копирование выполнялось как можно эффективнее. Там, где это возможно, делайте их максимально адаптируемыми путем наследования от unary_function или binary_function.
Стр.218
STL: контейнеры
Если вам нужен контейнер, по умолчанию используйте vector.
— Бьярн Страуструп (Bjarne Stroustrup),
[Stroustrup00] §17.7
Мы знаем, что вы уже предпочитаете использовать стандартные контейнеры вместо написанных вручную. Но какой именно контейнер следует использовать? Что должно (и что не должно) храниться в контейнерах и почему? Как следует заполнять контейнеры? Какие важные идиомы надо знать?
В этом разделе имеются ответы на все перечисленные (и не только) вопросы. И не случайно, что три первые рекомендации в нем содержат слова "Используйте vector...".
В этом разделе мы считаем наиболее значимой рекомендацию 79 — "Храните в контейнерах только значения или интеллектуальные указатели". К ней мы добавим — если даже вы не планируете применять [Boost] и [C++TR104] для иных целей, все равно воспользуйтесь их интеллектуальным указателем shared_ptr.
Стр.164
STL: Контейнеры
76. По умолчанию используйте vector. В противном случае выбирайте контейнер, соответствующий задаче
Очень важно использовать "правильный контейнер". Если у вас есть весомые причины выбрать определенный тип контейнера, используйте тот контейнер, который наиболее подходит для вашей задачи.
Если конкретных предпочтений нет, возьмите vector и спокойно работайте, зная, что вы сделали верный выбор.
77. Вместо массивов используйте vector и string
Избегайте реализации абстракции массива посредством массивов в стиле С, арифметики указателей и примитивов управления памятью. Использование vector или string не только сделает проще вашу жизнь, но и позволит написать более безопасную и масштабируемую программу.
78. Используйте vector (и string::c_str) для обмена данными с API на других языках
vector и string::с_str служат шлюзом для сообщения с API на других языках. Однако не полагайтесь на то, что итераторы являются указателями; для получения адреса элемента, на который ссылается vector<T>:.iterator iter, используйте выражение &*iter.
79. Храните в контейнерах только значения или интеллектуальные указатели
Храните к контейнерах объекты-значения. Контейнеры полагают, что их содержимое имеет тип значения, включая непосредственно хранящиеся значения, интеллектуальные указатели и итераторы.
80. Предпочитайте push_back другим способам расширения последовательности
Используйте push_back везде, где это возможно. Если для вас не важна позиция вставки нового объекта, лучше всего использовать для добавления элемента в последовательность функцию push_back. Все прочие средства могут оказаться как гораздо менее быстрыми, так и менее понятными.
81. Предпочитайте операции с диапазонами операциям с отдельными элементами
При добавлении элементов в контейнер лучше использовать операции с диапазонами (т.е. функцию insert, которая получает пару итераторов), а не последовательность вызовов функции для вставки одного элемента. Вызов функции для диапазона обычно проще написать, легче читать, и он более эффективен, чем явный цикл (см. также рекомендацию 84).
82. Используйте подходящие идиомы для реального уменьшения емкости контейнера и удаления элементов
Для того чтобы действительно избавиться от излишней емкости контейнера, воспользуйтесь трюком с использованием обмена, а для реального удаления элементов из контейнера — идиомой erase-remove.
Стр.217
Вопросы организации и стратегии
Если бы строители строили здания так же, как программисты пишут программы,— то первый же залетевший дятел разрушил бы всю цивилизацию.
— Джеральд Вайнберг (Gerald Weinberg)
Следуя великой традиции С и C++, мы начинаем отсчет с нуля. Главный совет — под номером 0 — говорит о том, что основной советчик по поводу стандартов кодирования — наши чувства и ощущения.
Остальная часть этой главы состоит из небольшого количества тщательно отобранных вопросов, которые не имеют прямого отношения к коду и посвящены важным инструментам и методам написания надежного кода.
В этом разделе книги наиболее важной мы считаем нулевую рекомендацию — "Не мелочитесь, или Что не следует стандартизировать".
Стр.14