Встроенные пространства имен - это функция управления версиями библиотеки, похожая на управление версиями символов , но реализованная исключительно на уровне C ++ 11 (т. Е. Кроссплатформенный), вместо того, чтобы быть функцией определенного двоичного исполняемого формата (т. Е. Зависящего от платформы).
Это механизм, с помощью которого автор библиотеки может выглядеть как вложенное пространство имен и действовать так, как если бы все его объявления были в окружающем пространстве имен (встроенные пространства имен могут быть вложенными, поэтому «более вложенные» имена просачиваются вплоть до первого -inline пространство имен и выглядят и действуют так, как будто их объявления были в любом из пространств имен между ними).
В качестве примера рассмотрим реализацию STL vector
. Если бы у нас были встроенные пространства имен с начала C ++, то в C ++ 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
реализация. Если кодовый была написано в предварительно C ++ 98 раз, и вы обнаружите , что C ++ 98 версии vector
вызывают проблемы для вас , когда вы обновить ваш компилятор, «все» вы должны сделать , это найти ссылки на std::vector
в ваша кодовая база и заменить их на std::pre_cxx_1997::vector
.
Приходите к следующему стандарту, и поставщик STL просто повторяет процедуру снова, вводя новое пространство имен std::vector
с emplace_back
поддержкой (для которого требуется C ++ 11) и вставляя этот iff __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
, я получаю одну или другую реализацию.
И ты был бы почти прав.
Рассмотрим следующий допустимый код пользователя C ++ 98 (было разрешено полностью специализировать шаблоны, которые уже существуют в пространстве имен std
в C ++ 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
или с функцией встроенного пространства имен C ++ 11, но не с трюком версионирования, который использовался using namespace <nested>
, потому что он раскрывает детали реализации, что истинное пространство имен, в котором vector
было определено, не было std
напрямую.
Существуют и другие дыры, с помощью которых вы можете обнаружить вложенное пространство имен (см. Комментарии ниже), но встроенные пространства имен закрывают их все. И это все, что нужно сделать. Чрезвычайно полезно для будущего, но AFAIK Стандарт не предписывает встроенные имена пространств имен для своей собственной стандартной библиотеки (хотя я бы хотел, чтобы это было ошибочно), поэтому его можно использовать только для сторонних библиотек, но не для сам стандарт (если поставщики компиляторов не договорились о схеме именования).
using namespace V99;
не работает в примере Страуструпа.std::cxx_11
. Не каждый компилятор всегда будет реализовывать все старые версии стандартных библиотек, хотя в настоящее время соблазнительно думать, что было бы очень небольшим бременем потребовать, чтобы существующие реализации ушли в старое при добавлении новых, поскольку на самом деле все они в любом случае. Я полагаю, что то, что мог бы с пользой сделать стандарт, сделало его необязательным, но со стандартным именем, если оно есть.using namespace A
в пространстве имен B делает имена в пространстве имен B скрывать имена в пространстве имен A, если вы ищетеB::name
- не так с встроенными пространствами имен).ifdef
s для полной реализации вектора? Все реализации будут находиться в одном пространстве имен, но только одна из них будет определена после предварительной обработкиusing
ключевое слово).http://www.stroustrup.com/C++11FAQ.html#inline-namespace (документ, написанный и поддерживаемый Бьярном Страуструпом, который, как вы думаете, должен знать о большинстве мотивов для большинства функций C ++ 11. )
В соответствии с этим, это разрешить управление версиями для обратной совместимости. Вы определяете несколько внутренних пространств имен и создаете самое новое
inline
. Или, в любом случае, по умолчанию для людей, которые не заботятся о версиях. Я полагаю, что самой последней из них может быть будущая или передовая версия, которая еще не установлена по умолчанию.Приведенный пример:
Я не сразу понимаю, почему вы не помещаете
using namespace V99;
внутрь пространства именMine
, но мне не нужно полностью понимать сценарий использования, чтобы поверить на слово Бьярне о мотивации комитета.источник
f(1)
версия будет вызываться из встроенногоV99
пространства имен?using namespace Mine;
, аMine
пространство имен содержит все из встроенного пространства именMine::V99
.inline
из файлаV99.h
в выпуске, который включаетV100.h
. Вы также можете одновременно изменитьMine.h
, чтобы добавить дополнительное включение.Mine.h
является частью библиотеки, а не частью клиентского кода.V100.h
, они устанавливают библиотеку под названием «Mine». В версии 99 "Mine" есть 3 заголовочных файлаMine.h
,V98.h
иV99.h
. Есть 4 файлов заголовка в версии 100 «Mine» -Mine.h
,V98.h
,V99.h
иV100.h
. Расположение заголовочных файлов - это деталь реализации, которая не имеет отношения к пользователям. Если они обнаруживают какую-то проблему совместимости, которая означает, что им нужно использовать конкретноMine::V98::f
из некоторого или всего их кода, они могут смешивать вызовыMine::V98::f
из старого кода с вызовамиMine::f
во вновь создаваемом коде.Mine
вместо того, чтобы специализироваться наMine::V99
илиMine::V98
.В дополнение ко всем остальным ответам.
Встроенное пространство имен может использоваться для кодирования информации ABI или версии функций в символах. По этой причине они используются для обеспечения обратной совместимости ABI. Встроенные пространства имен позволяют вводить информацию в искаженное имя (ABI) без изменения API, поскольку они влияют только на имя символа компоновщика.
Рассмотрим этот пример:
Предположим, вы пишете функцию,
Foo
которая принимает ссылку на объект скажетbar
и ничего не возвращает.Скажи в main.cpp
Если вы проверите свое имя символа для этого файла после компиляции его в объект.
Теперь, это может быть, что
bar
определяется как:В зависимости от типа сборки
bar
может ссылаться на два разных типа / макета с одинаковыми символами компоновщика.Чтобы предотвратить такое поведение, мы
bar
заключаем нашу структуру во встроенное пространство имен, где в зависимости от типа сборки символ компоновщикаbar
будет разным.Итак, мы могли бы написать:
Теперь, если вы посмотрите на файл объекта каждого объекта, вы создадите один, используя release, а другой - с флагом отладки. Вы обнаружите, что символы компоновщика включают в себя имя встроенного пространства имен. В таком случае
Обратите внимание на наличие
rel
иdbg
в именах символов.Теперь, если вы попытаетесь связать отладку с режимом выпуска или наоборот, вы получите ошибку компоновщика, в отличие от ошибки времени выполнения.
источник
На самом деле я обнаружил другое использование встроенных пространств имен.
С Qt вы получаете некоторые дополнительные полезные функции
Q_ENUM_NS
, которые, в свою очередь, требуют, чтобы в пространстве имен содержался мета-объект, который объявлен с помощьюQ_NAMESPACE
. Тем не менее, для того,Q_ENUM_NS
чтобы работать, должен быть соответствующийQ_NAMESPACE
в том же файле ⁽¹⁾. И может быть только один, или вы получите повторяющиеся ошибки определения. По сути, это означает, что все ваши перечисления должны быть в одном заголовке. Тьфу.Или ... вы можете использовать встроенные пространства имен. Скрытие перечислений в
inline namespace
мета-объектах приводит к тому, что метаобъекты имеют разные искаженные имена, а пользователи считают, что дополнительного пространства имен не существует ».Таким образом, они полезны для разбиения материала на несколько подпространств имен, которые все выглядят как одно пространство имен, если вам нужно сделать это по какой-то причине. Конечно, это похоже на запись
using namespace inner
во внешнем пространстве имен, но без СУХОГО нарушения записи имени внутреннего пространства имен дважды.Это на самом деле хуже, чем это; это должно быть в том же наборе скобок.
Если вы не попытаетесь получить доступ к мета-объекту без полной его квалификации, но мета-объект вряд ли когда-либо будет использоваться напрямую.
источник
Таким образом, чтобы подвести итог основным пунктам,
using namespace v99
иinline namespace
они не были одинаковыми, первый был обходным путем для библиотек версий до того, как в C ++ 11 было введено выделенное ключевое слово (inline), которое решало проблемы использованияusing
, в то же время обеспечивая ту же функциональность управления версиями. Использованиеusing namespace
используется, чтобы вызвать проблемы с ADL (хотя ADL теперь, кажется, следуетusing
директивам), и внеплановая специализация класса / функции библиотеки и т. Д. Пользователем не будет работать, если это будет сделано вне истинного пространства имен (чье имя пользователь не будет и не должен знать, то есть пользователю придется использовать B :: abi_v2 ::, а не просто B :: для разрешения специализации).Это покажет предупреждение статического анализа
first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
. Но если вы сделаете пространство имен A встроенным, то компилятор правильно разрешит специализацию. Хотя с расширениями C ++ 11 проблема исчезла.Вне строки определения не разрешаются при использовании
using
; они должны быть объявлены в блоке пространства имен вложенного / не вложенного расширения (что означает, что пользователю необходимо снова узнать версию ABI, если по какой-либо причине ему было разрешено предоставить собственную реализацию функции).Эта проблема исчезает, если сделать B встроенным.
Пространства
inline
имен других функциональных возможностей позволяют создателю библиотеки обеспечивать прозрачное обновление библиотеки 1) без принуждения пользователя к рефакторингу кода с новым именем пространства имен и 2) предотвращению отсутствия многословия и 3) обеспечению абстракции деталей, не относящихся к API, в то время как 4) предоставление такой же полезной диагностики линкера и поведения, которое обеспечит использование не встроенного пространства имен. Допустим, вы используете библиотеку:Это позволяет пользователю звонить
library::foo
без необходимости знать или включать версию ABI в документацию, которая выглядит чище. Использованиеlibrary::abiverison129389123::foo
будет выглядеть грязно.Когда выполняется обновление
foo
, т. Е. Добавление нового члена в класс, это не повлияет на существующие программы на уровне API, поскольку они еще не используют этот элемент, И изменение имени встроенного пространства имен ничего не изменит на уровне API. потому чтоlibrary::foo
все еще будет работать.Однако для программ, которые связываются с ним, поскольку встроенное имя пространства имен разбито на имена символов, как обычное пространство имен, изменение не будет прозрачным для компоновщика. Поэтому, если приложение не перекомпилировано, а связано с новой версией библиотеки, оно будет отображать символ
abi_v1
не найденной ошибки, а не связываться с ним, а затем вызывать загадочную логическую ошибку во время выполнения из-за несовместимости ABI. Добавление нового члена приведет к совместимости ABI из-за изменения определения типа, даже если это не повлияет на программу во время компиляции (уровень API).В этом сценарии:
Подобно использованию 2 не встроенных пространств имен, он позволяет связывать новую версию библиотеки без необходимости перекомпиляции приложения, поскольку
abi_v1
он будет искажен в одном из глобальных символов и будет использовать правильное (старое) определение типа. Однако перекомпиляция приложения приведет к разрешению ссылок наlibrary::abi_v2
.Использование
using namespace
менее функционально, чем использованиеinline
(в этом случае определения вне строки не разрешаются), но обеспечивает те же 4 преимущества, что и выше. Но реальный вопрос заключается в том, почему продолжать использовать обходной путь, если теперь для этого существует специальное ключевое слово. Это лучшая практика, менее многословная (приходится менять 1 строку кода вместо 2) и проясняющая намерение.источник