c++ 11允许内联命名空间,其所有成员也自动位于封闭的命名空间中。我想不出任何有用的应用程序——谁能给出一个简短的例子,说明需要内联名称空间的情况,以及它是最常用的解决方案的情况?
(此外,我不清楚在一个而不是所有声明中内联声明名称空间时会发生什么,这些声明可能位于不同的文件中。这不是自找麻烦吗?)
c++ 11允许内联命名空间,其所有成员也自动位于封闭的命名空间中。我想不出任何有用的应用程序——谁能给出一个简短的例子,说明需要内联名称空间的情况,以及它是最常用的解决方案的情况?
(此外,我不清楚在一个而不是所有声明中内联声明名称空间时会发生什么,这些声明可能位于不同的文件中。这不是自找麻烦吗?)
当前回答
除了所有其他的答案。
内联命名空间可用于编码ABI信息或符号中函数的版本。正是由于这个原因,它们被用于提供向后的ABI兼容性。内联名称空间允许您在不改变API的情况下将信息注入到mangded name (ABI)中,因为它们只影响链接器符号名称。
想想这个例子:
假设你写了一个函数Foo,它接受一个对象的引用,比如bar,但不返回任何东西。
在main.cpp中
struct bar;
void Foo(bar& ref);
如果在将该文件编译为对象后检查该文件的符号名称。
$ nm main.o
T__ Z1fooRK6bar
链接器符号名称可能不同,但它肯定会在某个地方编码函数和参数类型的名称。
现在,它可以被定义为:
struct bar{
int x;
#ifndef NDEBUG
int y;
#endif
};
根据构建类型的不同,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
链接器符号名称可能不同。
注意在符号名中出现了rel和dbg。
现在,如果你试图链接调试与发布模式,反之亦然,你会得到一个链接错误,而不是运行时错误。
其他回答
除了所有其他的答案。
内联命名空间可用于编码ABI信息或符号中函数的版本。正是由于这个原因,它们被用于提供向后的ABI兼容性。内联名称空间允许您在不改变API的情况下将信息注入到mangded name (ABI)中,因为它们只影响链接器符号名称。
想想这个例子:
假设你写了一个函数Foo,它接受一个对象的引用,比如bar,但不返回任何东西。
在main.cpp中
struct bar;
void Foo(bar& ref);
如果在将该文件编译为对象后检查该文件的符号名称。
$ nm main.o
T__ Z1fooRK6bar
链接器符号名称可能不同,但它肯定会在某个地方编码函数和参数类型的名称。
现在,它可以被定义为:
struct bar{
int x;
#ifndef NDEBUG
int y;
#endif
};
根据构建类型的不同,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
链接器符号名称可能不同。
注意在符号名中出现了rel和dbg。
现在,如果你试图链接调试与发布模式,反之亦然,你会得到一个链接错误,而不是运行时错误。
http://www.stroustrup.com/C++11FAQ.html#inline-namespace(一个由Bjarne Stroustrup编写和维护的文档,你会认为他应该知道c++ 11大多数特性的大多数动机。)
根据这一点,它允许进行版本控制以实现向后兼容。定义多个内部名称空间,并使最近的一个成为内联名称空间。或者不管怎样,对于不关心版本控制的人来说,这是默认的。我认为最近的一个可能是未来的或尖端的版本,还没有默认。
给出的例子是:
// 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
我不明白为什么不使用命名空间V99;但是我不需要完全理解这个用例,就能理解Bjarne对委员会动机的描述。
内联命名空间是一种类似于符号版本控制的库版本控制特性,但是完全是在c++ 11级别实现的。跨平台),而不是作为一个特定的二进制可执行格式(即。特定于平台的)。
通过这种机制,库作者可以使一个嵌套的名称空间看起来和操作起来就像它的所有声明都在周围的名称空间中一样(内联名称空间可以嵌套,因此“嵌套更多”的名称会一直渗透到第一个非内联名称空间,并看起来和操作起来就像它们的声明也在中间的任何名称空间中一样)。
例如,考虑vector的STL实现。如果我们从c++开始使用内联命名空间,那么在c++ 98中header <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的值,选择一个或另一个向量实现。如果你的代码库是用pre- 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用户代码(在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内联命名空间特性,但不使用使用namespace <nested>所使用的版本控制技巧,因为这暴露了实现细节,即定义vector所在的真正命名空间不是std。
还有其他漏洞可以用来检测嵌套的名称空间(参见下面的评论),但是内联名称空间堵塞了所有漏洞。这就是它的全部。对未来非常有用,但是AFAIK标准并没有为它自己的标准库规定内联命名空间名称(尽管我很想证明这是错误的),所以它只能用于第三方库,而不是标准本身(除非编译器供应商同意一个命名方案)。
实际上,我发现了内联命名空间的另一种用途。
在Qt中,您可以使用Q_ENUM_NS获得一些额外的、不错的特性,这反过来要求封闭的名称空间有一个元对象,该元对象是用Q_NAMESPACE声明的。但是,为了使Q_ENUM_NS起作用,在同一个文件⁽¹⁾中必须有一个对应的Q_NAMESPACE。只能有一个,否则就会出现重复的定义错误。这实际上意味着所有枚举都必须在同一个头文件中。讨厌的东西。
还是……您可以使用内联命名空间。在内联命名空间中隐藏枚举会导致元对象具有不同的扭曲名称,而用户会认为额外的命名空间不存在⁽²⁾。
因此,如果出于某种原因需要这样做,它们对于将内容分割为看起来都像一个名称空间的多个子名称空间非常有用。当然,这类似于在外部名称空间中使用内部名称空间进行写入,但是没有违反DRY,即两次写入内部名称空间的名称。
实际情况比这更糟;它必须在同一组大括号中。 除非您尝试在不完全限定元对象的情况下访问元对象,但是元对象很少被直接使用。
So to sum up the main points, using namespace v99 and inline namespace were not the same, the former was a workaround to version libraries before a dedicated keyword (inline) was introduced in C++11 which fixed the problems of using using, whilst providing the same versioning functionality. Using using namespace used to cause problems with ADL (although ADL now appears to follow using directives), and out-of-line specialisation of a library class / function etc. by the user wouldn't work if done outside of the true namespace (whose name the user wouldn't and shouldn't know, i.e. the user would have to use B::abi_v2:: rather than just B:: for the specialisation to resolve).
//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> {};
}
这将显示一个静态分析警告,在命名空间' a '之外的'myclass'类模板特化的第一个声明是c++11扩展[- 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内联时,这个问题就消失了。
内联命名空间的另一个功能是允许库作者对库提供一个透明的更新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
}
}
}
However, for programs that link with it, because the inline namespace name is mangled into symbol names like a regular namespace, the change will not be transparent to the linker. Therefore, if the application is not recompiled but is linked with a new version of the library, it will present a symbol abi_v1 not being found error, rather than it actually linking and then causing a mysterious logic error at runtime due to ABI incompatibility. Adding a new member will cause ABI compatibility due to the change in type definition, even if it doesn't affect the program at compile time (API level).
在这种情况下:
namespace library {
namespace abi_v1 {
class foo {
}
}
inline namespace abi_v2 {
class foo {
//new member
}
}
}
与使用2个非内联命名空间一样,它允许链接库的新版本,而不需要重新编译应用程序,因为abi_v1将在一个全局符号中被破坏,并且它将使用正确的(旧的)类型定义。然而,重新编译应用程序将导致引用解析为library::abi_v2。
使用Using命名空间的功能不如使用内联(因为行外定义不会解析),但提供了与上面相同的4个优点。但真正的问题是,现在有了专门的关键字,为什么还要继续使用变通方法呢?这是一种更好的实践,更少啰嗦(必须修改一行代码而不是两行),而且意图更明确。