"Оборотная сторона" этого вопроса рассматривается в рекомендации 57.
Ссылки
[Stroustrup00] §10.3.2, §11.2.4 • [Sutter00] §34 • [Sutter02] §39-40
59. Не используйте using
для пространств имен в заголовочных файлах или перед директивой #include
Резюме
Директива using
для пространств имен создана для вашего удобства, а не для головной боли других. Никогда не используйте объявления или директивы using
перед директивой #include
.
Вывод: не используйте директивы using
для пространств имен или using
-объявления в заголовочных файлах. Вместо этого полностью квалифицируйте все имена. (Второе правило следует из первого, поскольку заголовочные файлы не могут знать, какие другие директивы #include
могут появиться в тексте после них.)
Обсуждение
Вкратце: вы можете и должны свободно и без ограничений использовать объявления и директивы using
в своих файлах реализации после директив #include
. Несмотря на повторяющиеся заявления их противников, объявления и директивы using
не являются злом и не противоречат цели пространств имен. Они просто делают пространства имен более удобными в использовании.
Пространства имен представляют мощное средство для устранения неоднозначности имен. В большинстве случаев различные программисты выбирают различные имена для своих типов и функций, но в том редком случае, когда они выбрали одинаковые имена, и они должны вместе использоваться в некотором коде, пространства имен позволяют избежать коллизий. Для этого достаточно, чтобы вызывающий код явно квалифицировал имя, указав, имя из какого именно пространства должно использоваться в том или ином случае. Однако в подавляющем большинстве случаев никакой неоднозначности имен не наблюдается. Поэтому вполне можно использовать директивы и объявления using
, которые существенно облегчают использование пространств имен, снижая количество вводимого кода (программисту при использовании директив и объявлений using
не требуется всякий раз явное упоминание того, к какому пространству имен принадлежит то или иное имя). В редких случаях коллизий имен директивы и объявления using
не препятствуют указанию полностью квалифицированных имен для разрешения реально возникшей неоднозначности.
Однако директивы и объявления using
предназначены только для вашего удобства и вы не должны использовать их так, чтобы они влияли на какой-то другой код. В частности, их нельзя употреблять где попало, где за ними может следовать еще какой-то сторонний код. В частности, их не следует использовать в заголовочных файлах (которые предназначены для включения в неограниченное количество файлов реализации — вы не должны вносить путаницу в значение кода в этих файлах) или перед директивой #include
(тем самым вы по сути вносите их в текст этих заголовочных файлов).
Большинство программистов интуитивно понимают, почему директива 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
.
Читать дальше