Skip to the content.

Пространства имён


Конфликт имён возникает, когда два одинаковых идентификатора находятся в одной области видимости, и компилятор не может понять, какой из двух следует использовать в конкретной ситуации (нарушение 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;
}

Как компилятор ищёт имена:

  1. Если нашел имя в текущем namespace, то завершить поиск.
  2. Если не нашел имя и текущий namespace - глобальный, то выдать ошибку
  3. Если не нашел имя, но текущий 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;
}