C++11允许使用inline namespace
,其中所有成员也自动属于封闭的namespace
。我无法想出任何有用的应用场景-是否可以给出一个简洁明了的例子,说明何时需要使用inline namespace
以及它是最通用的解决方案?
(此外,我不清楚当在一个声明中但不是所有声明中都声明inline namespace
时会发生什么情况,这些声明可能位于不同的文件中。这难道不是在自找麻烦吗?)
C++11允许使用inline namespace
,其中所有成员也自动属于封闭的namespace
。我无法想出任何有用的应用场景-是否可以给出一个简洁明了的例子,说明何时需要使用inline namespace
以及它是最通用的解决方案?
(此外,我不清楚当在一个声明中但不是所有声明中都声明inline namespace
时会发生什么情况,这些声明可能位于不同的文件中。这难道不是在自找麻烦吗?)
内联命名空间是一个类库版本控制的特性,类似于符号版本控制,但它是在C++11级别上实现的(即跨平台),而不是特定二进制可执行文件格式的特性(即特定于平台)。
这是一种机制,通过它库作者可以使嵌套命名空间看起来和作为如果所有声明都在周围命名空间中(内联命名空间可以嵌套,因此“更嵌套”的名称一直到第一个非内联命名空间,看起来和行为就像它们的声明在中间的任何一个命名空间中一样)。
例如,考虑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
。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
的值,我将获得两个实现中的一个。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
vector
在命名空间std
中被声明,因此用户有权在该命名空间中专门化类型。std
或C++11内联命名空间特性,但无法使用使用using namespace <nested>
的版本控制技巧,因为它暴露了vector
真正被定义的名称空间不是直接std
的实现细节。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在委员会动机方面说的话。
f(1)
版本将从内联的 V99
命名空间中调用? - Eitan TV100.h
的发布版中,你从文件 V99.h
中删除了 inline
。当然,同时你也修改了 Mine.h
,添加了一个额外的 include。Mine.h
是库的一部分,而不是客户端代码的一部分。 - Steve JessopV100.h
,他们在安装一个名为“Mine”的库。在“Mine”版本99中有3个头文件-- Mine.h
, V98.h
和 V99.h
。在“Mine”版本100中有4个头文件-- Mine.h
, V98.h
, V99.h
和 V100.h
。这些头文件的排列顺序是一个与用户无关的实现细节。如果他们发现某些兼容性问题,需要从他们的一些或所有代码中特别使用Mine::V98::f
,他们可以将旧代码中调用Mine::V98::f
和新编写的代码中的Mine::f
混合调用。 - Steve JessopMine
中专门化模板,而不必在 Mine::V99
或 Mine::V98
中进行专门化。 - Justin Time - Reinstate Monica除了其他答案之外。
内联命名空间可用于在符号中编码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
};
}
现在,如果您查看使用发布标志构建的每个对象的对象文件以及使用调试标志构建的其他对象的对象文件,则会发现链接器符号也包括内联命名空间名称。在这种情况下。$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar
链接器符号名称可能不同。
请注意符号名称中存在rel
和dbg
。
现在,如果您尝试将调试模式与发布模式或反之链接,您将获得一个与运行时错误相反的链接器错误。
using namespace v99
和inline namespace
不是同一种方式。前者是在C++11引入专用关键字(即inline
)之前版本库的解决方法,解决了使用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> {};
}
在命名空间'A'外,类模板的第一个专门化声明是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变成内联代码后,问题就消失了。
inline
命名空间的另一个功能是允许库编写者提供对库的透明更新,而不需要强制用户重构使用新命名空间名称的代码,并且防止缺乏冗余度并提供 API 不相关细节的抽象,同时 4) 提供与使用非内联命名空间相同的有益链接器诊断和行为。假设您正在使用一个库:
namespace library {
inline namespace abi_v1 {
class foo {
}
}
}
library :: foo
,看起来更加简洁。使用library :: abiverison129389123 :: foo
会显得混乱。foo
进行更新时(例如向类中添加新成员),它不会影响API级别上的现有程序,因为它们不会使用该成员,而且内联命名空间名称的更改也不会在API级别上更改任何内容,因为library :: foo
仍将起作用。namespace library {
inline namespace abi_v2 {
class foo {
//new member
}
}
}
namespace library {
namespace abi_v1 {
class foo {
}
}
inline namespace abi_v2 {
class foo {
//new member
}
}
}
使用两个非内联命名空间可以使库的新版本链接而无需重新编译应用程序,因为 abi_v1
将被编码为其中一个全局符号,并且它将使用正确(旧)的类型定义。但重新编译应用程序将导致引用解析为library::abi_v2
。
使用 using namespace
不如使用 inline
功能强大(因为无法解决外部定义),但提供了与上述相同的 4 个优点。但真正的问题是,为什么继续使用变通方法,现在有专门的关键字来完成它呢。这是更好的实践,更少的冗余(只需要更改 1 行代码而不是 2 行),并且使意图明确。
我实际上发现了内联命名空间的另一个用途。
使用 Qt,您可以使用 Q_ENUM_NS
获得一些额外的好功能,这又要求封闭命名空间具有元对象,并使用 Q_NAMESPACE
声明。但是,为了使 Q_ENUM_NS
正常工作,必须在同一文件中存在相应的 Q_NAMESPACE
⁽¹⁾。否则,就会出现重复定义错误。这实际上意味着所有枚举值都必须在同一个头文件中。糟糕。
或者... 您可以使用内联命名空间。将枚举隐藏在 inline namespace
中会导致元对象具有不同的名称修饰符,同时对用户来说,额外的命名空间似乎不存在⁽²⁾。
因此,如果出于某种原因需要将内容拆分为多个子命名空间,并且所有子命名空间都“看起来”像一个命名空间,则它们非常有用。当然,这类似于在外部命名空间中编写 using namespace inner
,但避免了两次编写内部命名空间名称导致DRY违规的问题。
事实上还更糟糕,它必须在同一组大括号中。
除非您尝试访问元对象而不完全限定它,但元对象很少直接使用。
内联命名空间也可以用来在命名空间中提供更细粒度的特性/名称访问。
这在std::literals
中使用。 std中的literals
命名空间都是内联命名空间,因此:
using namespace std;
,则还可以访问std中所有用户定义的字面值。using namespace std::literals::string_literals;
,然后您将仅获取在该命名空间中定义的udl符号。对于您想要无限制访问的符号(udl、运算符等),这似乎是一种有用的技术,在其中您可以将它们打包成一个内联命名空间,以便您可以针对整个库的命名空间执行特定的使用,而不是整个库的命名空间。
std::cxx_11
给拖累。并非每个编译器都会一直实现所有旧版本的标准库,即使此时认为要求现有实现在添加新功能时保留旧功能开销很小,但实际上它们本身就已经有了。我想标准可以有用地做的是将其变成可选项,并且如果存在,则使用标准名称。 - Steve Jessopusing
语句会使得在寻找B::name
时,将会隐藏命名空间A中的同名对象,但是在内联命名空间中则不会。 - Johannes Schaub - litbusing
关键字)来使用任何要使用的实现。 - Vasily Biryukov