Namespaces
Пространства имён предоставляют метод предотвращения конфликтов имён в крупных проектах.
Объявления сущностей внутри блока пространства имён помещаются в область видимости пространства имён, что предотвращает их ошибочное принятие за одноимённые сущности в других областях видимости.
Объявления, находящиеся вне всех блоков пространств имён, принадлежат
глобальному пространству имён
. Глобальное пространство имён принадлежит
глобальной области видимости
и может быть явно указано с помощью ведущего
::
. Хотя у него нет объявления, глобальное пространство имён не является
неименованным пространством имён
.
Допускается несколько блоков пространства имён с одинаковым именем. Все объявления внутри этих блоков объявляются в одной и той же области видимости пространства имён.
Содержание |
Синтаксис
namespace
ns-name
{
declarations
}
|
(1) | ||||||||
inline
namespace
ns-name
{
declarations
}
|
(2) | (начиная с C++11) | |||||||
namespace
{
declarations
}
|
(3) | ||||||||
ns-name
::
member-name
|
(4) | ||||||||
using
namespace
ns-name
;
|
(5) | ||||||||
using
ns-name
::
member-name
;
|
(6) | ||||||||
namespace
name
=
qualified-namespace
;
|
(7) | ||||||||
namespace
ns-name
::
member-name
{
declarations
}
|
(8) | (начиная с C++17) | |||||||
namespace
ns-name
::
inline
member-name
{
declarations
}
|
(9) | (начиная с C++20) | |||||||
Объяснение
Пространства имен
inline
(необязательно)
namespace
attr
(необязательно)
identifier
{
namespace-body
}
|
|||||||||
inline
|
- |
(since C++11)
если присутствует, делает это встроенным пространством имен (см. ниже). Не может появляться в
extension-namespace-definition
если
original-namespace-definition
не использовал
inline
|
||
| attr | - | (since C++17) опциональная последовательность любого количества attributes | ||
| identifier | - |
либо
|
||
| namespace-body | - | возможно пустая последовательность declarations любого вида (включая определения классов и функций, а также вложенные пространства имен) |
Определения пространств имен разрешены только в области видимости пространства имен, включая глобальную область видимости.
Чтобы повторно открыть существующее пространство имён (формально, чтобы быть определением-расширением-пространства-имён ), поиск идентификатора , используемого в определении пространства имён, должен разрешаться в имя пространства имён (не псевдоним пространства имён), которое было объявлено как член охватывающего пространства имён или встроенного пространства имён внутри охватывающего пространства имён.
Тело пространства имён namespace-body определяет область видимости пространства имён , которая влияет на поиск имён .
Все имена, введённые объявлениями, которые появляются внутри namespace-body (включая вложенные определения пространств имён), становятся членами пространства имён identifier , независимо от того, является ли это определение пространства имён оригинальным определением (которое ввело identifier ) или расширяющим определением пространства имён (которое "переоткрыло" уже определённое пространство имён)
Член пространства имен, объявленный внутри тела пространства имен, может быть определен или переобъявлен вне его с использованием явной квалификации
namespace Q { namespace V // V является членом Q и полностью определен внутри Q { // namespace Q::V { // C++17 альтернатива строкам выше class C { void m(); }; // C является членом V и полностью определен внутри V // C::m только объявлен void f(); // f является членом V, но только объявлен здесь } void V::f() // определение члена f пространства имен V вне V // охватывающие пространства имен f все еще глобальное пространство имен, Q и Q::V { extern void h(); // Это объявляет ::Q::V::h } void V::C::m() // определение V::C::m вне пространства имен (и тела класса) // охватывающие пространства имен - глобальное пространство имен, Q и Q::V {} }
Определения и переобъявления вне пространства имен разрешены только
- после точки объявления,
- на уровне пространства имен, и
- в пространствах имен, охватывающих исходное пространство имен (включая глобальное пространство имен).
Кроме того, они должны использовать синтаксис qualified-id.
namespace Q { namespace V // исходное определение пространства имен для V { void f(); // объявление Q::V::f } void V::f() {} // OK void V::g() {} // Ошибка: g() еще не является членом V namespace V // расширенное определение пространства имен для V { void g(); // объявление Q::V::g } } namespace R // не является охватывающим пространством имен для Q { void Q::V::g() {} // Ошибка: нельзя определить Q::V::g внутри R } void Q::V::g() {} // OK: глобальное пространство имен охватывает Q
Имена, введённые с помощью friend объявлений внутри нелокального класса X, становятся членами ближайшего охватывающего пространства имён X, но они не становятся видимыми для обычного name lookup (ни unqualified , ни qualified ), если соответствующее объявление не предоставлено на уровне пространства имён, либо до, либо после определения класса. Такое имя может быть найдено с помощью ADL , которое учитывает как пространства имён, так и классы.
Только самая внутренняя охватывающая пространство имен учитывается таким объявлением дружественной функции при решении вопроса о том, будет ли имя конфликтовать с ранее объявленным именем.
void h(int); namespace A { class X { friend void f(X); // A::f является дружественной функцией class Y { friend void g(); // A::g является дружественной функцией friend void h(int); // A::h является дружественной функцией, нет конфликта с ::h }; }; // A::f, A::g и A::h не видны в области видимости пространства имён // несмотря на то, что они являются членами пространства имён A X x; void g() // определение A::g { f(x); // A::X::f находится через ADL } void f(X) {} // определение A::f void h(int) {} // определение A::h // A::f, A::g и A::h теперь видны в области видимости пространства имён // и также являются дружественными функциями для A::X и A::X::Y }
Встроенные пространства имен
Встроенное пространство имен — это пространство имен, которое использует необязательное ключевое слово
Члены встроенного пространства имен во многих ситуациях (перечисленных ниже) рассматриваются как члены охватывающего пространства имен. Это свойство транзитивно: если пространство имен N содержит встроенное пространство имен M, которое в свою очередь содержит встроенное пространство имен O, то члены O могут использоваться так, как если бы они были членами M или N.
// in C++14, std::literals and its member namespaces are inline { using namespace std::string_literals; // makes visible operator""s // from std::literals::string_literals auto str = "abc"s; } { using namespace std::literals; // makes visible both // std::literals::string_literals::operator""s // and std::literals::chrono_literals::operator""s auto str = "abc"s; auto min = 60s; } { using std::operator""s; // makes both std::literals::string_literals::operator""s // and std::literals::chrono_literals::operator""s visible auto str = "abc"s; auto min = 60s; } Примечание: правило о специализациях позволяет осуществлять версионирование библиотек: разные реализации шаблона библиотеки могут быть определены в разных встроенных пространствах имен, при этом позволяя пользователю расширять родительское пространство имен с помощью явной специализации основного шаблона:
Запустить этот код
namespace Lib { inline namespace Lib_1 { template<typename T> class A; } template<typename T> void g(T) { /* ... */ } } /* ... */ struct MyClass { /* ... */ }; namespace Lib { template<> class A<MyClass> { /* ... */ }; } int main() { Lib::A<MyClass> a; g(a); // ok, Lib is an associated namespace of A } |
(начиная с C++11) |
Безымянные пространства имён
unnamed-namespace-definition — это определение пространства имен вида
inline
(необязательно)
namespace
attr
(необязательно)
{
namespace-body
}
|
|||||||||
inline
|
- | (since C++11) если присутствует, делает это встроенным пространством имён |
| attr | - | (since C++17) опциональная последовательность любого количества атрибутов |
Это определение трактуется как определение пространства имён с уникальным именем и using-директивой в текущей области видимости, которая указывает на это безымянное пространство имён (Примечание: неявно добавленная using-директива делает пространство имён доступным для qualified name lookup и unqualified name lookup , но не для argument-dependent lookup ). Уникальное имя является уникальным во всей программе, но в пределах единицы трансляции каждое определение безымянного пространства имён отображается на одно и то же уникальное имя: множественные определения безымянных пространств имён в одной области видимости обозначают одно и то же безымянное пространство имён.
namespace { int i; // определяет ::(unique)::i } void f() { i++; // инкрементирует ::(unique)::i } namespace A { namespace { int i; // A::(unique)::i int j; // A::(unique)::j } void g() { i++; } // A::(unique)::i++ } using namespace A; // добавляет все имена из A в глобальное пространство имён void h() { i++; // ошибка: ::(unique)::i и ::A::(unique)::i обе находятся в области видимости A::i++; // ok, инкрементирует ::A::(unique)::i j++; // ok, инкрементирует ::A::(unique)::j }
|
Несмотря на то, что имена в безымянном пространстве имён могут быть объявлены с внешней линковкой, они никогда не доступны из других единиц трансляции, поскольку имя их пространства имён уникально. |
(until C++11) |
|
Безымянные пространства имён, а также все пространства имён, объявленные прямо или косвенно внутри безымянного пространства имён, имеют внутреннюю линковку , что означает, что любое имя, объявленное внутри безымянного пространства имён, имеет внутреннюю линковку. |
(since C++11) |
Объявления using
Вводит имя, определённое в другом месте, в декларативную область, где появляется эта using-декларация.
using
typename
(необязательно)
nested-name-specifier
unqualified-id
;
|
(до C++17) | ||||||||
using
declarator-list
;
|
(начиная с C++17) | ||||||||
typename
|
- |
ключевое слово
typename
может использоваться при необходимости для разрешения
зависимых имен
, когда using-объявление вводит тип-член из базового класса в шаблон класса
|
| nested-name-specifier | - |
последовательность имен и операторов разрешения области видимости
::
, заканчивающаяся оператором разрешения области видимости. Одиночный
::
ссылается на глобальное пространство имен.
|
| unqualified-id | - | id-выражение |
| declarator-list | - |
разделенный запятыми список из одного или более деклараторов вида
typename
(опционально)
nested-name-specifier
unqualified-id
. Декларатор может быть дополнен многоточием для указания
расширения пачки
, хотя эта форма имеет смысл только в
определениях производных классов
|
Using-объявления могут использоваться для введения членов пространства имён в другие пространства имён и области видимости блоков, или для введения членов базового класса в определения производных классов , или для введения enumerators в пространства имён, блоки и области видимости классов (since C++20) .
|
Объявление using с более чем одним спецификатором using эквивалентно соответствующей последовательности объявлений using с одним спецификатором using. |
(since C++17) |
Для использования в определениях производных классов см. using declaration .
Имена, введённые в область видимости пространства имён с помощью using-объявления, могут использоваться так же, как и любые другие имена, включая квалифицированный поиск из других областей видимости:
void f(); namespace A { void g(); } namespace X { using ::f; // глобальная f теперь видна как ::X::f using A::g; // A::g теперь видна как ::X::g using A::g, A::g; // (C++17) OK: двойное объявление разрешено в области пространства имён } void h() { X::f(); // вызывает ::f X::g(); // вызывает A::g }
Если после использования using-объявления для взятия члена из пространства имён это пространство имён расширяется и вводятся дополнительные объявления для того же имени, эти дополнительные объявления не становятся видимыми через using-объявление (в отличие от using-директивы). Исключением является случай, когда using-объявление именует шаблон класса: частичные специализации, введённые позже, эффективно видимы, поскольку их поиск осуществляется через первичный шаблон.
namespace A { void f(int); } using A::f; // ::f теперь является синонимом для A::f(int) namespace A // расширение пространства имен { void f(char); // не изменяет значение ::f } void foo() { f('a'); // вызывает f(int), несмотря на существование f(char) } void bar() { using A::f; // эта f является синонимом для A::f(int) и A::f(char) f('a'); // вызывает f(char) }
Using-объявления не могут называть template-id , или пространство имён , или scoped enumerator (до C++20) . Каждый декларатор в using-объявлении вводит одно и только одно имя, например using-объявление для enumeration не вводит ни одного из его перечислителей.
Все ограничения на обычные объявления одинаковых имен, правила сокрытия и перегрузки применяются к using-объявлениям:
namespace A { int x; } namespace B { int i; struct g {}; struct x {}; void f(int); void f(double); void g(char); // OK: имя функции g скрывает структуру g } void func() { int i; using B::i; // ошибка: i объявлена дважды void f(char); using B::f; // OK: f(char), f(int), f(double) являются перегрузками f(3.5); // вызывает B::f(double) using B::g; g('a'); // вызывает B::g(char) struct g g1; // объявляет g1 с типом struct B::g using B::x; using A::x; // OK: скрывает struct B::x x = 99; // присваивает A::x struct x x1; // объявляет x1 с типом struct B::x }
Если функция была введена с помощью using-объявления, объявление функции с тем же именем и списком параметров является некорректным (если только это не объявление той же самой функции). Если шаблон функции был введен с помощью using-объявления, объявление шаблона функции с тем же именем, списком типов параметров, возвращаемым типом и списком параметров шаблона является некорректным. Два using-объявления могут вводить функции с одинаковым именем и списком параметров, но при попытке вызова такой функции программа становится некорректной.
namespace B { void f(int); void f(double); } namespace C { void f(int); void f(double); void f(char); } void h() { using B::f; // вводит B::f(int), B::f(double) using C::f; // вводит C::f(int), C::f(double) и C::f(char) f('h'); // вызывает C::f(char) f(1); // ошибка: B::f(int) или C::f(int)? void f(int); // ошибка: f(int) конфликтует с C::f(int) и B::f(int) }
Если сущность объявлена, но не определена во внутреннем пространстве имён, а затем объявлена через using-объявление во внешнем пространстве имён, и затем появляется определение во внешнем пространстве имён с тем же неквалифицированным именем, это определение является членом внешнего пространства имён и конфликтует с using-объявлением:
namespace X { namespace M { void g(); // объявляет, но не определяет X::M::g() } using M::g; void g(); // Ошибка: попытка объявить X::g, которая конфликтует с X::M::g() }
В более общем случае, объявление, которое появляется в любой области видимости пространства имён и вводит имя с использованием неквалифицированного идентификатора, всегда вводит член в то пространство имён, в котором оно находится, а не в какое-либо другое. Исключениями являются явные инстанциации и явные специализации основного шаблона, определённого во встроенном пространстве имён: поскольку они не вводят новое имя, они могут использовать неквалифицированный идентификатор в охватывающем пространстве имён.
Директивы using
Директива using-directive является block-declaration со следующим синтаксисом:
attr
(необязательно)
using
namespace
nested-name-specifier
(необязательно)
namespace-name
;
|
(1) | ||||||||
| attr | - | (since C++11) любое количество атрибутов , применяемых к этой using-директиве |
| nested-name-specifier | - |
последовательность имён и операторов разрешения области видимости
::
, заканчивающаяся оператором разрешения области видимости. Одиночный
::
ссылается на глобальное пространство имён. При поиске имён в этой последовательности
lookup
рассматривает только объявления пространств имён
|
| namespace-name | - | имя пространства имён. При поиске этого имени lookup рассматривает только объявления пространств имён |
Директивы using разрешены только в области видимости пространства имен scope и в блочной области видимости. С точки зрения unqualified name lookup любого имени после директивы using и до конца области видимости, в которой она появляется, каждое имя из namespace-name видимо так, как если бы оно было объявлено в ближайшем охватывающем пространстве имен, которое содержит как директиву using, так и namespace-name .
Директива using не добавляет никакие имена в декларативную область, в которой она появляется (в отличие от объявления using), и поэтому не предотвращает объявление идентичных имен.
Директивы using транзитивны для целей неполного поиска : если область содержит директиву using, которая указывает на namespace-name , который сам содержит директиву using для некоторого namespace-name-2 , эффект такой, как если бы директивы using из второго пространства имён присутствовали в первом. Порядок, в котором встречаются эти транзитивные пространства имён, не влияет на поиск имён.
namespace A { int i; } namespace B { int i; int j; namespace C { namespace D { using namespace A; // Имена из A "внедряются" в D. // Неполный поиск в пределах D рассматривает эти имена как имеющие ту же // область видимости, что и глобальная область (например, для целей сокрытия имен). // Полный поиск с указанием D (D::name для некоторого имени) // найдет то же имя, что и неполный поиск в пределах D. int j; int k; int a = i; // i - это B::i, потому что A::i скрыто B::i int b = ::i; // ошибка: в глобальном пространстве имен все еще нет i } using namespace D; // имена из D и A внедряются в C int k = 89; // Можно объявить имя, идентичное введенному с помощью using int l = k; // неоднозначность: C::k или D::k int m = i; // ok: B::i скрывает A::i int n = j; // ok: D::j скрывает B::j } } // Все это эквивалентные определения: int t0 = B::i; int t1 = B::C::a; int t2 = B::C::D::a;
Если после использования using-директивы для указания некоторого пространства имен это пространство имен расширяется и в него добавляются дополнительные члены и/или using-директивы, эти дополнительные члены и дополнительные пространства имен становятся видимыми через using-директиву (в отличие от using-объявления)
namespace D { int d1; void f(char); } using namespace D; // вводит D::d1, D::f, D::d2, D::f, // E::e и E::f в глобальное пространство имён! int d1; // OK: нет конфликта с D::d1 при объявлении namespace E { int e; void f(int); } namespace D // расширение пространства имён { int d2; using namespace E; // транзитивная using-директива void f(int); } void f() { d1++; // ошибка: неоднозначность ::d1 или D::d1? ::d1++; // OK D::d1++; // OK d2++; // OK, d2 это D::d2 e++; // OK: e это E::e благодаря транзитивному using f(1); // ошибка: неоднозначность: D::f(int) или E::f(int)? f('a'); // OK: единственная f(char) это D::f(char) }
Примечания
Директива using
using
namespace
std
;
на любом уровне пространства имён добавляет все имена из пространства имён
std
в глобальное пространство имён (поскольку глобальное пространство имён является ближайшим пространством, содержащим как
std
, так и любое пользовательское пространство имён), что может привести к нежелательным конфликтам имён. Эта и другие using-директивы обычно считаются плохой практикой на уровне файла в заголовочных файлах (
SF.7: Не используйте
using
namespace
на глобальном уровне в заголовочных файлах
).
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_namespace_attributes
|
201411L
|
(C++17) | Атрибуты для пространств имён |
Ключевые слова
Пример
Этот пример показывает, как использовать пространство имён для создания класса, который уже был назван в
std
пространстве имён.
#include <vector> namespace vec { template<typename T> class vector { // ... }; } // of vec int main() { std::vector<int> v1; // Standard vector. vec::vector<int> v2; // User defined vector. // v1 = v2; // Error: v1 and v2 are different object's type. { using namespace std; vector<int> v3; // Same as std::vector v1 = v3; // OK } { using vec::vector; vector<int> v4; // Same as vec::vector v2 = v4; // OK } }
Отчеты о дефектах
Следующие отчеты об изменениях в поведении, являющиеся дефектными, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 101 | C++98 |
программа является некорректной, если объявление функции в области видимости
пространства имен или блока и функция, введенная с помощью using-объявления, объявляют одну и ту же функцию (без неоднозначности) |
разрешено |
| CWG 373 | C++98 |
поиск рассматривал объявления пространств имен только для
последнего имени в операнде using-директивы (что является неоптимальным, поскольку классы не могут содержать пространства имен) |
ограничение поиска
применяется ко всем именам в операндах using-директив |
| CWG 460 | C++98 | using-объявление могло именовать пространство имен | запрещено |
| CWG 565 | C++98 |
using-объявление не может вводить функцию,
идентичную другой функции в той же области видимости, но это ограничение не применялось к шаблонам функций |
применить то же ограничение
и к шаблонам функций |
| CWG 986 | C++98 | using-директива была транзитивной для квалифицированного поиска | транзитивна только для неквалифицированного поиска |
| CWG 987 | C++98 |
сущности, объявленные во вложенном пространстве имен, были
также членами охватывающего пространства имен |
вложенные области видимости исключены |
| CWG 1021 | C++98 |
было неясно, считается ли сущность, определение которой
вводится в пространство имен через using-объявление, определенной в этом пространстве имен |
не определена в этом пространстве имен |
| CWG 1838 | C++98 |
неквалифицированное определение во внешнем пространстве имен
могло определять сущность, объявленную, но не определенную в другом пространстве имен и подтянутую с помощью using |
неквалифицированное определение
всегда относится к своему пространству имен |
| CWG 2155 | C++98 |
решение
проблемы CWG 1838
не было
применено к объявлениям классов и перечислений |
применено |
Смотрите также
| namespace alias | создает псевдоним существующего пространства имен |