Пространства имён
Конфликт имён возникает, когда два одинаковых идентификатора находятся в одной области видимости, и компилятор не может понять, какой из двух следует использовать в конкретной ситуации (нарушение ODR - One Definition Rule):
int func(int a, int b)
{
return a + b;
}
int func(int a, int b)
{
return a - b;
}
С ростом программы увеличивается количество идентификаторов, следовательно, увеличивается вероятность возникновения конфликтов имен.
В С для избежания конфликта имён выбирают один префикс на весь проект, например библиотека Expat использует префикс XML:
struct XML_Parser;
int XML_GetCurrentLineNumber(XML_Pareser * parser);
Для решения этой проблемы в С++ добавили пространства имён:
namespace XML
{
struct Parser;
int GetCurrentLineNumber(Parser * parser);
}
// Внешний код:
XML::Parser;
Пространство имён определяет область кода, в которой гарантируется уникальность всех идентификаторов. По умолчанию, глобальные переменные и обычные функции определены в глобальном пространстве имён:
int x = 4;
int func(int x)
{
return -x;
}
Язык С++ позволяет объявлять собственные пространства имен через ключевое слово namespace. Все что объявлено внутри пользовательского пространства имён принадлежит только этому пространству имён, а не глобальному.
Исправим нашу ошибку:
namespace First
{
int func(int a, int b)
{
return a + b;
}
}
namespace Second
{
int func(int a, int b)
{
return a - b;
}
}
Теперь если мы попытаемся вызвать функцию как обычно (func()
), то компилятор будет искать её в глобальной области и не найдёт. Есть несколько способов сообщить компилятору какую из версий func()
использовать:
First::func();
Second::func();
::func(); // глобальное пространство имён
using First::func();
func();
Спорное решение: использование using-директивы, поскольку с её помощью мы подключаем все имена из пространства имен, вероятность возникновения конфликтов имён возрастает:
using namespace First;
func();
Наглядный пример, показывающий почему это не хорошо:
namespace First
{
int func1(int x)
{
return -x;
}
}
namespace Second
{
int func2(int x)
{
return x*x;
}
}
Допустим мы используем два разных пространства имён и подключаем их таким образом:
using namespace First;
using namespace Second;
Тем самым, мы вытаскиваем абсолютно всё что есть в First:: и Second:: в глобальное пространство имён. Все работает отлично, мы можем использовать func1() из First:: и func2() из Second::. Но, однажды мы решили обновить нашу First:: и добавили туда так же func2():
namespace First
{
int func1(int x)
{
return -x;
}
int func2(int x)
{
return x + 5;
}
}
Теперь и First:: и Second:: импортируют func2() в глобальное пространство, у нас возник конфликт имён. В лучшем случае все закончится на этапе компиляции ошибкой, а в худшем компилятор посчитает что вызывать одну из func2() будет лучше чем другую и будет молча вызывать её вместо другой.
Так же никогда не стоит использовать using-стейтменты вне тела функций, поскольку как только он был объявлен, его невозможно отменить или заменить в пределах области видимости, в которой он был объявлен. Лучшее решение, это использование локальной области видимости:
int main ()
{
{
using namespace First;
// Здесь все относится к First:: и нет способов это отменить
} // здесь заканчивается действие First
}
Допускается объявления пространства имён в нескольких разных местах (и файлах):
// file1.cpp
namespace Math
{
int add(int x, int y)
{
return x + y;
}
}
// file2.cpp
namespace Math
{
int sub(int x, int y)
{
return x - y;
}
}
// main.cpp
int main()
{
Math::add();
Math::sub();
return 0;
}
Одни пространства имён могут быть вложены в другие:
namespace First
{
namespace Second
{
int x = 5;
}
}
First::Second::x;
// С++ позволяет создать псевдонимы для пространства имён:
namespace FS = First::Second;
FS::x;
// начиная с С++17
First::x;
Определение пространств имён можно разделять. Это одно пространство имён:
namespace weapons
{
struct Bow {};
}
namespace weapons
{
struct Sword {};
}
Класс\ структура определяют одноимённое пространство имён.
Может понадобиться наоборот обратиться к глобальному пространству имён:
struct Dictionary {};
namespace items
{
struct Dictionary {};
::Dictionary d;
}
Как компилятор ищёт имена:
- Если нашел имя в текущем namespace, то завершить поиск.
- Если не нашел имя и текущий namespace - глобальный, то выдать ошибку
- Если не нашел имя, но текущий namespace - не глобальный, то перейти на уровень выше - в родительский namespace, перейти на шаг 1
Стоит запомнить, что поиск продолжается до первого совпадения, а в перегрузке участвуют только найденные к этому моменту функции:
int foo(int i) { return 1; } // наиболее подходящая функция
namespace first
{
int foo(float f) { return 2; } // вызовется эта функция, несмотря на то что foo(int) лучше
int foo(double a, double b) { return 3; }
namespace second
{
int x = foo(5);
}
}
А так вообще будет ошибка компиляции:
int foo(int i) { return 1; } // не дойдёт сюда
namespace first
{
// найдет первым и выдаст ошибку по количеству параметров
int foo(double a, double b) { return 3; }
namespace second
{
int x = foo(5);
}
}
Argument-dependent name lookup (ADL, Поиск Кёнига)
Допустим у нас в пространстве math, реализован вектор на плоскости, а так же оператор +:
namespace math
{
struct Vector2 {};
Vector2 operator+(Vector2 a, const Vector 2 & b);
}
Возникает вопрос: как при использовании оператора +, компилятор его находит, ведь мы не указывали namespace для этого метода?
math::Vector2 a(1, 2);
math::Vector2 b(3, 4);
b = math::operator+(a, b); // OK
b = a + b; // что эквивалентно b = operator+(a, b);
Тут срабатывает ADL - при поиске имени функции рассматриваются имена из текущего пространства, а так же пространства к которым принадлежат аргументы функции!
unnamed namespace
Если определеить пространство и не указать его имя, то компилятор сгенерирует уникальное имя для него и после пространства использует using namespace:
Это:
namespace
{
int x;
}
Аналогично этому:
namespace __unique_compiler_generated_identifer0x42
{
int x;
}
using namespace __unique_compiler_generated_identifer0x42;
Безымянные пространства имён - замена для static. Поведение переменных объявленных в безымянном пространстве имён подобно поведению static - переменная считается локальной для текущей единицы трансляции, однако unnamed namespace имеет ряд преимуществ перед static.
static:
static int x; // OK
static void my_func() {} // OK
static class MyClass {}; // ERROR
static struct MyStruct {}; // ERROR
unnamed namespace:
namespace
{
int x; // OK
void my_func() {} // OK
class MyClass {}; // OK
static struct MyStruct {}; // OK
} // namespace
inline namespace
Допустим мы сделали функцию my_func() и успешно использовали в коде, однако захотелось её кардинально переделать, как оставить поддержку с старым кодом?
Можно добавить еще одну функцию: my_func_v2(), однако это может закончится кучей функций с кучей имён.
Для этого используют:
inline namespace v1
{
void my_func() {}
} // namespace v1
namespace v2
{
void my_func() {}
} // namespace v2
int main()
{
v1::my_func();
v2::my_func();
// calls the inline version (v1)
my_func();
return 0;
}