内联命名空间用于什么?

c++ 11允许__abc0,它的所有成员也自动在封装的namespace中。我想不出任何有用的应用,谁能给一个简短的,简洁的例子的情况下,inline namespace是需要的,在哪里它是最常用的解决方案?

(此外,我不清楚当一个namespace在一个而不是所有声明中声明inline时会发生什么,这些声明可能存在于不同的文件中。这不是自找麻烦吗?)

70741 次浏览

http://www.stroustrup.com/C++11FAQ.html#inline-namespace(由Bjarne Stroustrup编写和维护的文档,你会认为他应该知道大多数c++ 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中,但我不需要完全理解用例,就能理解Bjarne对委员会动机的解释。

内联命名空间是一个类似于象征版本控制的库版本控制特性,但是完全在c++ 11级别实现。跨平台),而不是作为一个特定的二进制可执行格式(即。特定于平台的)。

通过这种机制,库作者可以使一个嵌套的名称空间看起来和操作起来就像它的所有声明都在周围的名称空间中一样(内联名称空间可以嵌套,因此“嵌套更多”的名称会一直渗透到第一个非内联名称空间,并看起来和操作起来就像它们的声明也在中间的任何名称空间中一样)。

作为一个例子,考虑vector的STL实现。如果我们从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实现。如果你的代码库是用pre- c++ 98编写的,并且你发现c++ 98版本的vector在你升级编译器时给你带来了麻烦,“所有”你要做的就是在你的代码库中找到std::vector的引用,并用std::pre_cxx_1997::vector替换它们。

对于下一个标准,STL供应商只是重复这个过程,为std::vector引入一个支持emplace_back的新名称空间(这需要c++ 11),并将该名称空间内联到__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用户代码(在c++ 98中已经允许完全专门化位于命名空间std中的模板):

// 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标准并没有为它自己的标准库规定内联命名空间名称(尽管我很想证明这是错误的),所以它只能用于第三方库,而不是标准本身(除非编译器供应商同意一个命名方案)。

除了所有其他的答案。

内联命名空间可用于编码ABI信息或符号中函数的版本。正是由于这个原因,它们被用于提供向后的ABI兼容性。内联名称空间允许您在不改变API的情况下将信息注入到mangded name (ABI)中,因为它们只影响链接器符号名称。

想想这个例子:

假设你写了一个函数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和debug标记来构建一个对象。您将发现链接器符号还包括内联命名空间名称。在这种情况下

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

链接器符号名称可能不同。

注意符号名中存在reldbg

现在,如果你试图链接调试与发布模式,反之亦然,你会得到一个链接错误,而不是运行时错误。

实际上,我发现了内联命名空间的另一种用途。

对于Qt,你可以使用Q_ENUM_NS获得一些额外的、很好的特性,这反过来要求封闭的命名空间有一个元对象,这个元对象是用Q_NAMESPACE声明的。然而,为了让Q_ENUM_NS工作,必须有一个对应的Q_NAMESPACE 在同一个文件中⁽¹⁾。只能有一个,否则就会出现重复的定义错误。这实际上意味着所有枚举都必须在同一个头文件中。讨厌的东西。

< em >或…< / em >你可以使用内联命名空间。在inline namespace中隐藏枚举会导致元对象具有不同的扭曲名称,而用户会认为额外的名称空间不存在⁽²⁾。

因此,如果你出于某种原因需要这样做,它们对于将东西分割成多个子名称空间非常有用,所有都像一个名称空间。当然,这类似于在外部命名空间中写入using namespace inner,但不会违反,将内部命名空间的名称写入两次。


  1. 实际情况比这更糟;它必须在同一组大括号中。

  2. 除非您尝试在不完全限定元对象的情况下访问元对象,但是元对象很少被直接使用。

所以总结一下主要的要点,using namespace v99inline namespace是不一样的,前者是在c++ 11中引入专用关键字(内联)之前的版本库的一种变通方法,它修复了使用using的问题,同时提供了相同的版本控制功能。使用using namespace过去会导致ADL出现问题(尽管ADL现在似乎遵循using指令),如果在真正的命名空间之外(用户不会也不应该知道其名称,即用户必须使用B::abi_v2::而不是B::来解决专门化),用户对库类/函数的出行专门化等将无法工作。

//library code
namespace B { //library name the user knows
namespace A { //ABI version the user doesn't know about
template<class T> class myclass{int a;};
}
using namespace A; //pre inline-namespace versioning trick
}


// user code
namespace B { //user thinks the library uses this namespace
template<> class myclass<int> {};
}

这将显示一个静态分析警告first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]。但是如果将名称空间A设置为内联,则编译器将正确地解析专门化。不过,有了c++ 11扩展,这个问题就不存在了。

当使用using时,行外定义不会解析;它们必须在嵌套/非嵌套扩展名称空间块中声明(这意味着用户需要再次知道ABI版本,如果出于某种原因,他们被允许提供自己的函数实现)。

#include <iostream>
namespace A {
namespace B{
int a;
int func(int a);
template<class T> class myclass{int a;};
class C;
extern int d;
}
using namespace B;
}
int A::d = 3; //No member named 'd' in namespace A
class A::C {int a;}; //no class named 'C' in namespace 'A'
template<> class A::myclass<int> {}; // works; specialisation is not an out-of-line definition of a declaration
int A::func(int a){return a;}; //out-of-line definition of 'func' does not match any declaration in namespace 'A'
namespace A { int func(int a){return a;};} //works
int main() {
A::a =1; // works; not an out-of-line definition
}

当使B内联时,这个问题就消失了。

inline命名空间具有的另一个功能是允许库作者对库提供透明的更新1)不强迫用户用新的命名空间名称重构代码2)防止缺少冗长3)提供与api无关的细节抽象,同时4)提供与使用非内联命名空间相同的有益链接器诊断和行为。假设你正在使用一个库:

namespace library {
inline namespace abi_v1 {
class foo {
}
}
}

它允许用户调用library::foo,而不需要知道或在文档中包含ABI版本,这看起来更干净。使用library::abiverison129389123::foo看起来很脏。

当对foo进行更新时,即向类中添加一个新成员,它不会在API级别上影响现有的程序,因为它们不会已经使用该成员,并且内联命名空间名称的更改不会在API级别上改变任何内容,因为library::foo仍然有效。

namespace library {
inline namespace abi_v2 {
class foo {
//new member
}
}
}

但是,对于与它链接的程序,由于内联命名空间名称像常规命名空间一样被分解为符号名称,因此更改对链接器来说不是透明的。因此,如果应用程序没有重新编译,而是链接到库的新版本,它将显示一个符号abi_v1未被发现的错误,而不是实际链接,然后在运行时由于ABI不兼容而导致一个神秘的逻辑错误。由于类型定义的更改,添加新成员将导致ABI兼容性,即使它不会在编译时影响程序(API级别)。

在这种情况下:

namespace library {
namespace abi_v1 {
class foo {
}
}


inline namespace abi_v2 {
class foo {
//new member
}
}
}

就像使用2个非内联命名空间一样,它允许链接库的新版本,而不需要重新编译应用程序,因为abi_v1将在一个全局符号中被破坏,并且它将使用正确的(旧的)类型定义。然而,重新编译应用程序将导致引用解析为library::abi_v2

使用using namespace的功能比使用inline少(因为行外定义不会解析),但提供了与上面相同的4个优点。但真正的问题是,现在有了专门的关键字,为什么还要继续使用变通方法呢?这是一种更好的实践,更少啰嗦(必须修改一行代码而不是两行),而且意图更明确。

内联名称空间还可以用于提供对名称空间内特性/名称的细粒度访问。

这在std::literals中使用。std中的literals命名空间都是内联命名空间,因此:

  • 如果你在某处使用using namespace std;,你也可以访问std中所有用户定义的字面量。
  • 但如果你只是需要一组udl在你的本地代码,你也可以using namespace std::literals::string_literals;,你将只是获得定义在该命名空间的udl符号。

对于想要访问非限定(udl、操作符等)的符号,这似乎是一种有用的技术,您可以将它们捆绑在一个内联名称空间中,这样您就可以只对该(子)名称空间而不是整个库的名称空间进行特定的使用。