после объявления using
.
Рассмотрим два частных случая. Пусть фрагменты 1, 2 и 3 находятся в трех различных заголовочных файлах s1.h
, s2.h
и s3.h
, а фрагмент 4 — в файле реализации s4.срр
, который включает указанные заголовочные файлы. Тогда семантика B::g
зависит от порядка, в котором заголовочные файлы включены в s4.срр
! В частности:
• если s3.h
идет перед s2.h
, то B::g
будет вызывать A::f(int)
;
• иначе если s1.h
идет перед s2.h
, то B::g
будет вызывать A::f(doublе)
;
• иначе B::g
не будет компилироваться вовсе.
В описанной ситуации имеется один вполне определенный порядок, при котором все работает так, как должно.
Давайте теперь рассмотрим ситуацию, когда фрагменты 1, 2, 3 и 4 находятся в четырех различных заголовочных файлах s1.h
, s2.h
, s3.h
и s4.h
. Теперь все становится существенно хуже: семантика B::g
зависит от порядка включения заголовочных файлов не только в s4.h
, но и в любой код, который включает s4.h
! В частности, файл реализации client_code.срр
может пытаться включить заголовочные файлы в любом порядке:
• если s3.h
идет перед s2.h
, то B::g
будет вызывать A::f(int)
;
• иначе если s1.h
идет перед s2.h
, то B::g
будет вызывать A::f(doublе)
;
• иначе B::g
не будет компилироваться вовсе.
Ситуация стала хуже потому, что два файла реализации могут включать заголовочные файлыclient_code_1.срр
включает s1.h
, s2.h
и s4.h
в указанном порядке, a client_code_2.срр
включает в соответствующем порядке s3.h
, s2.h
и s4.h
? Тогда B::g
нарушает правило одного определения (one definition rule — ODR), поскольку имеются две несогласующиеся несовместимые реализации, которые не могут быть верными одновременно: одна из них пытается вызвать A::f (int)
, а вторая — A::f(doublе)
.
Поэтому никогда не используйте директивы и объявления using
для пространств имен в заголовочных файлах либо перед директивой #include
в файле реализации. В случае нарушения этого правила вы несете ответственность за возможное изменение смысла следующего за using
кода, например, вследствие загрязнения пространства имен или неполного списка импортируемых имен. (Обратите внимание на 'директивы и объявления using
using
для внесения, при необходимости, имен из базового класса.)
Во всех заголовочных файлах, как и в файлах реализации до последней директивы #include
, всегда используйте явные полностью квалифицированные имена. В файлах реализации после всех директив #include
вы можете и должны свободно использовать директивы и объявления using
. Это верный способ сочетания краткости кода с модульностью.
Перенесение большого проекта со старой до-ANSI/ISO реализации стандартной библиотеки (все имена которой находятся в глобальном пространстве имен) к использованию новой (где практически все имена находятся в пространстве имен std
) может заставить вас аккуратно разместить директиву using
в заголовочном файле. Этот способ описан в [Sutter02].
60. Избегайте выделения и освобождения памяти в разных модулях
Золотое правило программиста — положи, где взял. Выделение памяти в одном модуле, а освобождение в другом делает программу более хрупкой, создавая тонкую дальнюю зависимость между этими модулями. Такие модули должны быть компилируемы одной и той же версией компилятора с одними и теми же флагами (в частности, отладочные версии и версии NDEBUG
) и с одной и той же реализацией стандартной библиотеки; кроме того, с практической точки зрения лучше, чтобы модуль, выделяющий память, оставался загружен при ее освобождении.
Разработчики библиотек хотят улучшить их качество, и, как прямое следствие, внутренние структуры данных и алгоритмы, используемые стандартными распределителями памяти, могут существенно различаться в разных версиях. Более того, к значительным изменениям во внутренней работе распределителей памяти могут приводить даже различные опции компилятора (например, включение или отключение отладочных возможностей).
Следовательно, о функции освобождения памяти (т.е. операторе ::operator delete
или функции std::free
) при пересечении границ модулей практически нельзя строить какие-либо предположения, в особенности при пересечении границ модулей, при котором вы не можете гарантировать, что они будут скомпилированы одним и тем же компилятором С++ с одними и теми же опциями. Конечно, часто эти модули находятся в одном и том же файле проекта и компилируются с одними и теми же опциями, но комфорт часто приводит к забывчивости. В особенности высока цена такой забывчивости при переходе к динамически связываемым библиотекам, распределении большого проекта между несколькими группами или при замене модулей 'на ходу' — в этом случае вы должны уделить максимум внимания тому, чтобы выделение и освобождение памяти выполнялось в пределах одного модуля или подсистемы.
Хорошим методом обеспечения освобождения памяти соответствующей функцией является использование shared_ptr
(см. [C++TR104]). Интеллектуальный указатель shared_ptr
со счетчиком ссылок может захватить свой 'удалитель' в процессе конструирования. 'Удалитель' — это функциональный объект (или обычный указатель на функцию), который выполняет освобождение памяти. Поскольку упомянутый функциональный объект, или указатель на функцию, является частью состояния объекта shared_ptr
, модуль, выделивший память объекту, может одновременно определить функцию освобождения памяти, и эта функция будет корректно вызвана, даже если точка освобождения находится где-то в другом модуле — вероятно, относительно небольшой ценой (корректность важнее цены; см. также рекомендации 5, 6 и 8). Конечно, исходный модуль при этом должен оставаться загруженным.
61. Не определяйте в заголовочном файле объекты со связыванием
Объекты со связыванием, включая переменные или функции уровня пространства имен, обладают