Для чего нужны встроенные пространства имен?

С++ 11 допускает inline namespace s, все члены которого также автоматически входят в namespace. Я не могу придумать какое-либо полезное приложение этого - может ли кто-нибудь дать краткий, краткий пример ситуации, когда требуется inline namespace и где это самое идиоматическое решение?

(Кроме того, мне не ясно, что произойдет, когда namespace объявлен inline в одном, но не во всех объявлениях, которые могут жить в разных файлах. Разве это не попрошайничество?)

Ответ 1

Внутренние пространства имен - это функция управления версиями библиотек, сродни символьной версии, но реализована исключительно на уровне С++ 11 (т.е. кросс-платформенная) вместо того, чтобы быть функции определенного бинарного исполняемого формата (т.е. для конкретной платформы).

Это механизм, с помощью которого автор библиотеки может создать вложенное пространство имен и действовать так, как если бы все его объявления находились в окружающем пространстве имен (inline namespaces могут быть вложенными, поэтому "более вложенные" имена перколяются до полного первое не-встроенное пространство имен и выглядеть так, как если бы их объявления также находились в любом из пространств имен между ними.)

В качестве примера рассмотрим реализацию STL vector. Если бы мы имели встроенные пространства имен с начала С++, то в С++ 98 заголовок <vector> мог бы выглядеть так:

namespace std {

#if __cplusplus < 1997L // pre-standard C++
    inline
#endif

    namespace pre_cxx_1997 {
        template <class T> __vector_impl; // implementation class
        template <class T> // e.g. w/o allocator argument
        class vector : __vector_impl<T> { // private inheritance
            // ...
        };
    }
#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)
#  if __cplusplus == 1997L // C++98/03
    inline
#  endif

    namespace cxx_1997 {

        // std::vector now has an allocator argument
        template <class T, class Alloc=std::allocator<T> >
        class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
            // ...
        };

        // and vector<bool> is special:
        template <class Alloc=std::allocator<bool> >
        class vector<bool> {
            // ...
        };

    };

#endif // C++98/03 or later

} // namespace std

В зависимости от значения __cplusplus выбирается одна или другая реализация vector. Если ваша кодовая база была написана в pre-С++ 98 раз, и вы обнаружите, что версия С++ 98 vector вызывает у вас проблемы при обновлении вашего компилятора, "все", которое вам нужно сделать, это найти ссылки на std::vector в вашей кодовой базе и заменить их на std::pre_cxx_1997::vector.

Приходите к следующему стандарту, и поставщик STL снова повторяет эту процедуру, вводя новое пространство имен для std::vector с поддержкой emplace_back (что требует С++ 11) и вставляет это if if __cplusplus == 201103L.

ОК, так зачем мне нужна новая языковая функция? Я уже могу сделать следующее, чтобы иметь тот же эффект, не?

namespace std {

    namespace pre_cxx_1997 {
        // ...
    }
#if __cplusplus < 1997L // pre-standard C++
    using namespace pre_cxx_1997;
#endif

#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)

    namespace cxx_1997 {
        // ...
    };
#  if __cplusplus == 1997L // C++98/03
    using namespace cxx_1997;
#  endif

#endif // C++98/03 or later

} // namespace std

В зависимости от значения __cplusplus, я получаю либо одну, либо другую из реализаций.

И вы будете почти правы.

Рассмотрим следующий допустимый код пользователя С++ 98 (было разрешено полностью специализировать шаблоны, которые живут в пространстве имен std в С++ 98 уже):

// I don't trust my STL vendor to do this optimisation, so force these 
// specializations myself:
namespace std {
    template <>
    class vector<MyType> : my_special_vector<MyType> {
        // ...
    };
    template <>
    class vector<MyOtherType> : my_special_vector<MyOtherType> {
        // ...
    };
    // ...etc...
} // namespace std

Это вполне допустимый код, в котором пользователь поставляет свою собственную реализацию вектора для набора типов, где она, по-видимому, знает более эффективную реализацию, чем тот, который найден в (ее копии) STL.

Но: когда вы специализируетесь на шаблоне, вам нужно сделать это в пространстве имен, в котором оно было объявлено. Стандарт говорит, что vector объявлен в пространстве имен std, так что, когда пользователь по праву ожидает специализации типа.

Этот код работает с неидентифицированным пространством имен std или с встроенным пространством имен С++ 11, но не с трюком версии, который использовал using namespace <nested>, потому что он предоставляет детали реализации, что истинное пространство имен в который был определен vector, не был напрямую std.

Существуют и другие отверстия, в которых вы можете обнаружить вложенное пространство имен (см. комментарии ниже), но встроенные пространства имен объединяют их все. И это все. Очень полезно в будущем, но AFAIK Standard не предписывает имена встроенных имен для собственной стандартной библиотеки (я бы хотел, чтобы это было доказано неправильно), поэтому его можно использовать только для сторонних библиотек, а не для сам стандарт (если поставщики компилятора не согласуют схему именования).

Ответ 2

http://www.stroustrup.com/C++11FAQ.html#inline-namespace (документ, написанный и поддерживаемый Бьярном Страуступом, который, как вы думаете, должен знать большинство мотивов для большинства функций С++ 11. )

В соответствии с этим, он должен разрешить управление версиями для обратной совместимости. Вы определяете несколько внутренних пространств имен и создаете самый последний inline. Или, во всяком случае, по умолчанию для людей, которые не заботятся об управлении версиями. Я предполагаю, что самым последним может быть будущая или передовая версия, которая еще не дефолт.

Приведенный пример:

// file V99.h:
inline namespace V99 {
    void f(int);    // does something better than the V98 version
    void f(double); // new feature
    // ...
}

// file V98.h:
namespace V98 {
    void f(int);    // does something
    // ...
}

// file Mine.h:
namespace Mine {
#include "V99.h"
#include "V98.h"
}

#include "Mine.h"
using namespace Mine;
// ...
V98::f(1);  // old version
V99::f(1);  // new version
f(1);       // default version

Я не сразу понимаю, почему вы не помещаете using namespace V99; внутри пространства имен Mine, но мне не нужно полностью понимать прецедент, чтобы взять слово Бьярне для него на мотивацию комитета.

Ответ 3

В дополнение ко всем ответам выше.

Встроенное пространство имен может использоваться для кодирования информации ABI или версии функций в символах. По этой причине они используются для обеспечения обратной совместимости ABI. Встроенные пространства имен позволяют вводить информацию в искаженное имя (ABI) без изменения API, поскольку они влияют только на имя символа компоновщика.

Рассмотрим этот пример:

Предположим, вы пишете функцию Foo которая берет ссылку на объект скажем bar и ничего не возвращает.

Скажи в main.cpp

struct bar;
void Foo(bar& ref);

Если вы проверите имя вашего символа для этого файла после компиляции его в объект.

$ nm main.o
T__ Z1fooRK6bar 

Имя символа компоновщика может отличаться, но оно наверняка будет где-то кодировать имя функции и тип аргумента.

Теперь, это может быть, что bar определяется как:

struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};

В зависимости от типа сборки bar может ссылаться на два разных типа/макета с одинаковыми символами компоновщика.

Чтобы предотвратить такое поведение, мы обертываем нашу структурную bar в линейное пространство имен, где в зависимости от типа сборки символ компоновщика bar будет различным.

Итак, мы могли бы написать:

#ifndef NDEBUG
inline namespace rel { 
#else
inline namespace dbg {
#endif
struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};
}

Теперь, если вы посмотрите на файл объекта каждого объекта, вы создадите один, используя release, а другой - с флагом отладки. Вы обнаружите, что символы компоновщика включают в себя имя встроенного пространства имен. В этом случае

$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar

Имена символов компоновщика могут быть разными.

Обратите внимание на наличие rel и dbg в именах символов.

Теперь, если вы попытаетесь связать отладку с режимом выпуска или наоборот, вы получите ошибку компоновщика, в отличие от ошибки времени выполнения.

Ответ 4

На самом деле я обнаружил другое использование встроенных пространств имен.

С Qt вы получаете некоторые дополнительные полезные функции, используя Q_ENUM_NS, что, в свою очередь, требует, чтобы в пространстве имен в составе был мета-объект, который объявлен с помощью Q_NAMESPACE. Однако, чтобы Q_ENUM_NS работал, в том же файле должен быть соответствующий Q_NAMESPACE ". И может быть только один, или вы получите повторяющиеся ошибки определения. Фактически это означает, что все ваши перечисления должны быть в одном заголовке. Тьфу.

Или... вы можете использовать встроенные пространства имен. Скрытие перечислений в inline namespace приводит к тому, что метаобъекты имеют разные искаженные имена, в то время как пользователи считают, что дополнительное пространство имен не существует⁽²⁾.

Таким образом, они полезны для разбиения материала на несколько подпространств имен, которые все выглядят как одно пространство имен, если вам нужно сделать это по какой-то причине. Конечно, это похоже на запись using namespace inner во внешнем пространстве имен, но без нарушения СУХОЙ записи имени внутреннего пространства имен дважды.


  1. Это на самом деле хуже, чем это; это должно быть в том же наборе скобок.

  2. Если вы не попытаетесь получить доступ к мета-объекту без полной его квалификации, но мета-объект вряд ли когда-либо будет использоваться напрямую.