c++ 11允许内联命名空间,其所有成员也自动位于封闭的命名空间中。我想不出任何有用的应用程序——谁能给出一个简短的例子,说明需要内联名称空间的情况,以及它是最常用的解决方案的情况?
(此外,我不清楚在一个而不是所有声明中内联声明名称空间时会发生什么,这些声明可能位于不同的文件中。这不是自找麻烦吗?)
c++ 11允许内联命名空间,其所有成员也自动位于封闭的命名空间中。我想不出任何有用的应用程序——谁能给出一个简短的例子,说明需要内联名称空间的情况,以及它是最常用的解决方案的情况?
(此外,我不清楚在一个而不是所有声明中内联声明名称空间时会发生什么,这些声明可能位于不同的文件中。这不是自找麻烦吗?)
当前回答
实际上,我发现了内联命名空间的另一种用途。
在Qt中,您可以使用Q_ENUM_NS获得一些额外的、不错的特性,这反过来要求封闭的名称空间有一个元对象,该元对象是用Q_NAMESPACE声明的。但是,为了使Q_ENUM_NS起作用,在同一个文件⁽¹⁾中必须有一个对应的Q_NAMESPACE。只能有一个,否则就会出现重复的定义错误。这实际上意味着所有枚举都必须在同一个头文件中。讨厌的东西。
还是……您可以使用内联命名空间。在内联命名空间中隐藏枚举会导致元对象具有不同的扭曲名称,而用户会认为额外的命名空间不存在⁽²⁾。
因此,如果出于某种原因需要这样做,它们对于将内容分割为看起来都像一个名称空间的多个子名称空间非常有用。当然,这类似于在外部名称空间中使用内部名称空间进行写入,但是没有违反DRY,即两次写入内部名称空间的名称。
实际情况比这更糟;它必须在同一组大括号中。 除非您尝试在不完全限定元对象的情况下访问元对象,但是元对象很少被直接使用。
其他回答
实际上,我发现了内联命名空间的另一种用途。
在Qt中,您可以使用Q_ENUM_NS获得一些额外的、不错的特性,这反过来要求封闭的名称空间有一个元对象,该元对象是用Q_NAMESPACE声明的。但是,为了使Q_ENUM_NS起作用,在同一个文件⁽¹⁾中必须有一个对应的Q_NAMESPACE。只能有一个,否则就会出现重复的定义错误。这实际上意味着所有枚举都必须在同一个头文件中。讨厌的东西。
还是……您可以使用内联命名空间。在内联命名空间中隐藏枚举会导致元对象具有不同的扭曲名称,而用户会认为额外的命名空间不存在⁽²⁾。
因此,如果出于某种原因需要这样做,它们对于将内容分割为看起来都像一个名称空间的多个子名称空间非常有用。当然,这类似于在外部名称空间中使用内部名称空间进行写入,但是没有违反DRY,即两次写入内部名称空间的名称。
实际情况比这更糟;它必须在同一组大括号中。 除非您尝试在不完全限定元对象的情况下访问元对象,但是元对象很少被直接使用。
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标准并没有为它自己的标准库规定内联命名空间名称(尽管我很想证明这是错误的),所以它只能用于第三方库,而不是标准本身(除非编译器供应商同意一个命名方案)。
除了所有其他的答案。
内联命名空间可用于编码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。
现在,如果你试图链接调试与发布模式,反之亦然,你会得到一个链接错误,而不是运行时错误。
内联名称空间还可以用于提供对名称空间内特性/名称的细粒度访问。
这用于std::字面量。std中的文字命名空间都是内联命名空间,因此:
如果您使用使用命名空间std;在某个地方,您还可以访问std中所有用户定义的字面量。 但是如果你只需要在本地代码中使用一组udl,你也可以使用命名空间std::literals::string_literals;您将得到在该名称空间中定义的udl符号。
对于想要访问非限定(udl、操作符等)的符号,这似乎是一种有用的技术,您可以将它们捆绑在一个内联名称空间中,这样您就可以只对该(子)名称空间而不是整个库的名称空间进行特定的使用。