Пространства имен
Мы уже встречались с областями видимости типов, в которые встроена концепция пространств имен.
Однако если нужен примитив языка, который просто дает концепцию пространства имен,
можно использовать namespace.
Базовое использование
Заголовок раздела «Базовое использование»Пространство имен — это, по сути, область видимости, вмещающая функции, переменные, типы и другие пространства имен и позволяющая избегать коллизий имен между ними.
Я вовсю использовал пространство имен std в примерах.
std — пространство имен из стандартной библиотеки, но и свое создать несложно.
#include <iostream>
// Определяем пространство имен Demo.namespace Demo{ // Помещаем функцию внутрь Demo. void f() { std::cout << "Hello from Demo::f" << std::endl; }}
int main(){ // Доступ к функции можно получить через оператор разрешения области видимости. // Это аналогично использованию `.` для доступа к полям структуры. Demo::f(); return 0;}Заметьте, это практически то же самое, что статическая функция в объявлении типа. Так получается потому, что объявления типов могут работать как пространства имен.
#include <iostream>
struct Demo{ static void f() { std::cout << "Hello from Demo::f" << std::endl; }};
int main(){ Demo::f(); return 0;}Пространства имен можно вкладывать друг в друга.
namespace Demo{ namespace Demo1 { void f() { } }}
int main(){ Demo::Demo1::f(); return 0;}Пространства имен пригождаются, когда у вас есть разные реализации функции с одним именем, которые нужно различать:
namespace Impl1{ void f() { std::cout << "Hello"; }}namespace Impl2{ // Несмотря на то же имя, ошибки линковки не будет. void f() { std::cout << "World"; }}int main(){ Impl1::f(); // Hello Impl2::f(); // World return 0;}Пространства имен можно определять многократно, в разных местах, и все они будут обозначать один и тот же набор сущностей.
namespace Demo{ void f() { }}
namespace Dima{ void g() { }}
// Обозначает то же пространство имен, что и Demo выше.namespace Demo{ // Это пространство имен — не то же самое, что Dima выше. // Это пространство имен на самом деле называется Demo::Dima. namespace Dima { // Полностью допустимо, ошибки времени компиляции нет void g() { } }
// Тоже допустимо, ошибки времени компиляции нет. void g() { }
// Дублирующееся определение, ошибка времени компиляции. // Функция `f()` уже есть выше. void f() { }}А значит, определять пространство имен std в своем коде можно совершенно спокойно.
Для некоторых вещей это даже требуется, например для определения хеш-функции для типа.
namespace std{ void f(){}}
int main(){ std::f(); return 0;}🤓 технически дописывать в
stdпроизвольные сущности — неопределенное поведение, так делать не стоит. Правило защищает стандартную библиотеку: реализация вправе считать, что контролирует все содержимоеstd, и объявлять там что-то свое. Если вашаstd::fкогда-нибудь столкнется с внутреннейfиз заголовков библиотеки, компилятор не обязан даже предупредить. (Определять то, что уже существует, — и так очевидная ошибка.) Пример выше считайте упрощением.
Заметьте, main должен находиться вне любых пространств имен, чтобы играть особую роль точки входа.
Одно из явно разрешенных стандартом исключений — специализация std::hash
для вашего собственного типа: без нее ваш тип нельзя положить в std::unordered_set
или использовать как ключ std::unordered_map.
#include <cstddef>#include <functional>#include <iostream>#include <string>
struct Person{ std::string name; int age;};
namespace std{ // Разрешенное стандартом расширение: // специализация `std::hash` для вашего собственного типа. template <> struct hash<Person> { size_t operator()(const Person& person) const { return std::hash<std::string>{}(person.name) ^ (std::hash<int>{}(person.age) << 1); } };}
int main(){ Person person{"John", 20}; std::cout << std::hash<Person>{}(person) << std::endl; return 0;}Заметьте, main должен находиться вне любых пространств имен, чтобы играть особую роль точки входа.
Пространства имен позволяют использовать сущности без уточнения
Заголовок раздела «Пространства имен позволяют использовать сущности без уточнения»namespace Demo{ void f() { }
void g() { // Можно использовать `f` без уточнения. f(); // При желании можно было бы использовать `Demo::f`. Demo::f(); }}Это аналогично тому, что внутри тела метода не нужно писать this->
при обращении к полям или методам.
Кстати, то же самое относится к областям имен, создаваемым struct или class.
Еще вложенные пространства имен видят сущности, определенные во внешних пространствах. Это аналогично областям видимости в функциях, с той разницей, что определять функции с одинаковым прототипом в разных областях можно.
namespace Outer{ void f() { }
void g() { }
namespace Inner { // Переопределить `g` во вложенном пространстве имен можно. void g() { // Вызывает `Outer::f()` f(); } }}Сокращение с помощью оператора ::
Заголовок раздела «Сокращение с помощью оператора ::»Не обязательно вкладывать каждое пространство имен по отдельности.
В определении пространства имен можно использовать ::.
Следующий код:
namespace Outer{ namespace Inner { void f() { } }}эквивалентен:
namespace Outer::Inner{ void f() { }}Глобальная область видимости
Заголовок раздела «Глобальная область видимости»Глобальная область видимости — это область вне любых пространств имен. У нее нет имени. Она помогает устранять неоднозначность в некоторых нишевых ситуациях.
namespace Demo{ void f() { std::cout << "Outer"; }
namespace Dima { namespace Demo { void f() { std::cout << "Inner"; } }
void g() { // Мы хотим вызвать `Demo::f` из глобальной области видимости. // Однако простой вызов `Demo::f` вызовет `f` из внутреннего `Demo`. Demo::f(); // Inner
// Чтобы вызвать внешний `Demo::f`, придется воспользоваться глобальной областью видимости. ::Demo::f(); // Outer
// Внутренний вызов можно записать так: ::Demo::Dima::Demo::f(); // Inner } }}using namespace
Заголовок раздела «using namespace»using namespace — довольно крутая штука.
Она втягивает все содержимое пространства имен в текущую область видимости.
«Втягивает» — значит делает все члены пространства имен видимыми, то есть
доступными без уточнения.
Самое классное: это можно делать почти в любой области видимости —
в отдельных функциях, во вложенных обычных областях, в пространствах имен.
Исключение — области видимости классов: внутри класса using-директива запрещена,
там используйте квалифицированные имена или using-объявления.
#include <iostream>
int main(){ { // Делаем всех членов std видимыми здесь без уточнения. using namespace std;
cout << "Hello" << endl; }
{ // Но не здесь. cout << "World" << endl; // ошибка времени компиляции }}using namespace довольно опасна, потому что может привести к тому,
что при разрешении перегрузки будет выбрана не ваша функция:
компилятор может вызвать стандартную функцию с тем же именем, что у вашей,
которая лучше подошла по параметрам.
Покажу это на примере поспокойнее, чтобы вы уловили идею.
#include <iostream>
namespace Demo{ void f(int a) { std::cout << "Demo::f -- " << a << std::endl; }}
// Мы спокойно объявляем вторую функцию, принимающую вместо этого float,// и ожидаем, что вызовется она, ведь о функции в Demo мы не знаем.void f(float a){ std::cout << "Happy life" << std::endl;}
int main(){ using namespace Demo; // Угадайте, какая функция вызовется здесь? f(5); return 0;}Именно из-за этого using namespace std крайне опасна:
в std:: лежит куча всякой всячины,
которая с большой вероятностью совпадет по именам с чем-то вашим.
Можно провести бесчисленные мучительные часы,
разбираясь, почему ваша функция делает не то, что должна,
а потом осознать, что вы все это время тестировали вовсе не свою функцию.
Применить using namespace к типам нельзя.
#include <iostream>
struct T{ int value; static void f() { std::cout << "T::f" << std::endl; }};
int main(){ // Не компилируется using namespace T; f(); return 0;}Разрешение коллизий имен
Заголовок раздела «Разрешение коллизий имен»Если вы включили два пространства имен с одинаковым объявлением функции, вызвать функцию без уточнения не получится.
namespace A{ void f() { std::cout << "A"; }}namespace B{ void f() { std::cout << "B"; }}
int main(){ using namespace A; using namespace B;
// Какую вызывать, брат? A::f или B::f? f();
// Придется устранить неоднозначность, уточнив имя. A::f(); // A B::f(); // B}Похожая ситуация, где коллизия имен возникает между функцией из глобальной области и функцией из используемого пространства имен, разрешается аналогично:
namespace A{ void f() { std::cout << "Hello"; }}void f(){ std::cout << "World";}
int main(){ using namespace A;
f(); // бессмыслица! A::f(); // Hello ::f(); // World
return 0;}using для псевдонимов имен
Заголовок раздела «using для псевдонимов имен»Еще одно очень полезное применение using — дать типу другое имя.
Это во многом эквивалентно typedef, но using чуть интуитивнее,
поэтому рекомендуется использовать using вместо typedef.
С шаблонами
typedefне работает, аusing— да.
namespace Long::Namespace::Name{ struct Outer { struct Inner { struct Target { int x; } } }}int main(){ using T = Long::Namespace::Name::Outer::Inner::Target; // то же, что // typedef Long::Namespace::Name::Outer::Inner::Target T;
T t{15}; std::cout << t.x; // 15
return 0;}enum class Color{ Red = 'r', Green = 'g', Blue = 'b',};
int main(){ using C = Color; C color = C::Red;
switch (color) { case C::Red: std::cout << "Red"; break; case C::Green: std::cout << "Green"; break; case C::Blue: std::cout << "Blue"; break; }
return 0;}Для пространств имен для того же есть синтаксис namespace X =.
namespace Hello::World::Long::Namespace::Very::Long{ void func() { }}
int main(){ namespace NS = Hello::World::Long::Namespace::Very::Long; NS::func();}