Большинство программистов интуитивно понимают, почему директива using
(например, using namespace A;
) вызывает загрязнение в случае воздействия на код, следующий за ней и не осведомленный о наличии этой директивы: поскольку эта директива полностью импортирует одно пространство имен в другое, включая даже те имена, которые до сих пор не были видны, понятно, что это может легко изменить смысл следующего за директивой кода.
Но вот одна распространенная ошибка: многие считают, что использование объявления using
на уровне пространства имен (например, using N::Widget;
) вполне безопасно. Однако это не так. Такие объявления, как минимум, опасны, причем более тонким и хитрым способом. Рассмотрим следующий код:
// Фрагмент 1
namespace A {
int f(double);
}
// Фрагмент 2
namespace B {
using A::f;
void g();
}
// Фрагмент 3
namespace A {
int f(int);
}
// Фрагмент 4
void В::g() {
f(1); // какая перегрузка будет вызвана?
}
Здесь опасность заключается в том, что объявление using
использует текущий список имен f
в пространстве имен А
в тот момент, когда это объявление встречается. Таким образом, какая именно перегрузка будет видима в пространстве имен В
, зависит от того, где именно находится приведенный код фрагментов и в каком порядке он скомбинирован. (Здесь должен раздаться предупреждающий рев вашей внутренней сирены — "Зависимость от порядка есть зло!") Вторая перегрузка, f(int)
, в большей степени соответствует вызову f(1)
, но f(int)
будет невидима для B::g
, если ее объявление окажется после объявления 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].