59. Не используйте using для пространств имен в заголовочных файлах или перед директивой #include
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.
Рассмотрим два частных случая. Пусть фрагменты 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].
Ссылки
[Stroustrup00] §9.2.1 • [Sutter02] §39-40
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Каталог Include
Каталог Include В каталоге Include описаны многочисленные файлы. Одни из них используются почти во всех примерах, другие нужны только для одной или двух программ. Перечень наиболее важных файлов приводится ниже.1. EvryThng.h, как говорит само его название, включает почти все
Элемент <xsl:include>
Элемент <xsl:include> Другой способ вставить таблицы стилей внутрь других документов — использовать элемент <xsl:include>, позволяющий включить содержимое файла в определенное место в таблице стилей. У этого элемента только один атрибут:• href (обязательный). URI таблицы
Элемент <xsl:namespace>: создание объявлений пространств имен
Элемент <xsl:namespace>: создание объявлений пространств имен В XSLT 2.0 включен еще один новый элемент: <xsl:namespace>, позволяющий добавлять в результирующий документ объявления пространств имен. Однако на текущий момент больше об этом элементе ничего не известно, так что я не
9.3. Поиск заголовочных и библиотечных файлов
9.3. Поиск заголовочных и библиотечных файлов Заголовочные файлы в системе Linux хранятся в иерархии каталогов /usr/include. Именно там по умолчанию компилятор ищет включаемые файлы. (Заголовочные файлы могут храниться за пределами /usr/include, но тогда на них имеются ссылки внутри
Другие классы и функции пространств имен WMI
Другие классы и функции пространств имен WMI WMI является неисчерпаемой темой для обсуждения, так как содержит просто огромное количество классов, не говоря уже о количестве функций, реализованных в этих классах. Для рассмотрения всех функций WMI (не говоря уже об объектах
24. Используйте только внутреннюю, но не внешнюю защиту директивы #include
24. Используйте только внутреннюю, но не внешнюю защиту директивы #include РезюмеПредотвращайте непреднамеренное множественное включение ваших заголовочных файлов директивой #include, используя в них защиту с уникальными именами.ОбсуждениеКаждый заголовочный файл должен
19.2.3.5. Проверяйте орфографию в документации и README-файлах перед выпуском версии
19.2.3.5. Проверяйте орфографию в документации и README-файлах перед выпуском версии Проверяйте грамотность документации, README-файлов и сообщений об ошибках в программе. Сырой код, т.е. код, который вызывает появление сообщений об ошибках при компиляции и имеет орфографические
19.2.3.5. Проверяйте орфографию в документации и README-файлах перед выпуском версии
19.2.3.5. Проверяйте орфографию в документации и README-файлах перед выпуском версии Проверяйте грамотность документации, README-файлов и сообщений об ошибках в программе. Сырой код, т.е. код, который вызывает появление сообщений об ошибках при компиляции и имеет орфографические
2.4. Предотвращение конфликта имен с помощью пространств имен
2.4. Предотвращение конфликта имен с помощью пространств имен ПроблемаВ несвязанных между собой модулях обнаружены конфликтующие имена или требуется заранее избежать возможности таких конфликтов, создав логические группы кода.РешениеДля структурирования кода
Определение пространств имен
Определение пространств имен Итак, вы определили вид своего компоновочного блока (и необходимые внешние ссылки). Теперь можно создать пространство имен .NET (МуNamespace), используя для этого директиву .namespace.// Наш компоновочный блок имеет одно пространство имен. .namespace MyNamespace
Обзор пространств имен GDI+
Обзор пространств имен GDI+ Платформа .NET обеспечивает целый набор пространств имен для поддержки визуализации двумерной графики. В дополнение к основным функциональным возможностям разработчика, которые обычно предлагаются графическими пакетами (цвета, шрифты, перья,
ВКЛЮЧЕНИЕ ФАЙЛА: #include
ВКЛЮЧЕНИЕ ФАЙЛА: #include Когда препроцессор "распознает" директиву #include, он ищет следующее за ней имя файла и включает его в текущий файл. Директива выдается в двух видах: #include <stdio.h> имя файла в угловых скобках#include "mystuff.h" имя файла в двойных
8.2.3. Несколько слов о заголовочных файлах
8.2.3. Несколько слов о заголовочных файлах Заголовочный файл предоставляет место для всех extern-объявлений объектов, объявлений функций и определений встроенных функций. Это называется локализацией объявлений. Те исходные файлы, где объект или функция определяется или
Узлы пространств имен
Узлы пространств имен Каждому пространству имен, которое определено для данного элемента, соответствует узел пространства имен, ассоциируемый с узлом этого элемента. Множество узлов пространств имен, которое ассоциируется с данным элементом, включает в себя следующие
Псевдонимы пространств имен
Псевдонимы пространств имен Любопытным фактом является то, что XML-документ, являющийся результатом выполнения XSLT-преобразования, может и сам быть XSLT- преобразованием. Иными словами, преобразования могут генерироваться другими преобразованиями. В некоторых случаях