Wal*_*ter 320 c++ namespaces c++11 inline-namespaces
C++ 11允许inline namespace
s,其所有成员也自动在封闭中namespace
.我想不出任何有用的应用 - 有人可以给出一个简短,简洁的例子,说明inline namespace
需要哪种情况以及最常用的解决方案?
(另外,当发生了什么并不清楚,我namespace
声明inline
在一个但不是所有的声明,这可能住在不同的文件.这难道不是找麻烦?)
Mar*_*utz 329
内联名称空间是类似于符号版本控制的库版本控制功能,但纯粹在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
Run Code Online (Sandbox Code Playgroud)
根据值__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
Run Code Online (Sandbox Code Playgroud)
根据值__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
Run Code Online (Sandbox Code Playgroud)
这是完全有效的代码,其中用户为一组类型提供其自己的向量实现,其中她显然知道比在STL(她的副本)中找到的更有效的实现.
但是:当专门化一个模板时,你需要在它声明的命名空间中这样做.标准说这vector
是在命名空间中声明的std
,所以这是用户正确期望专门化类型的地方.
此代码适用于非版本化命名空间std
,或使用C++ 11内联命名空间功能,但不适用于使用的版本控制技巧using namespace <nested>
,因为它公开了实现详细信息,其中vector
定义的真实命名空间不是std
直接的.
您可以通过其他漏洞检测嵌套命名空间(请参阅下面的注释),但内联命名空间会将它们全部插入.这就是它的全部内容.对于未来非常有用,但AFAIK标准没有规定其自己的标准库的内联命名空间名称(尽管我喜欢被证明是错误的),因此它只能用于第三方库,而不是标准本身(除非编译器供应商同意命名方案).
Ste*_*sop 65
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
Run Code Online (Sandbox Code Playgroud)
我不会立即明白你为什么不把using namespace V99;
命名空间置于命名空间内Mine
,但我不必完全理解用例,以便在委员会的动机中采用Bjarne的话.
Lew*_*sey 10
总结一下要点,using namespace v99
两者inline namespace
并不相同,前者是在 C++11 中引入专用关键字(inline)之前对版本库的解决方法,它解决了使用问题using
,同时提供相同的版本控制功能。使用using namespace
used会导致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> {};
}
Run Code Online (Sandbox Code Playgroud)
这将显示静态分析警告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
}
Run Code Online (Sandbox Code Playgroud)
当使 B 内联时,问题就消失了。
命名空间的其他功能inline
是允许库编写者为库提供透明的更新:1) 无需强迫用户使用新的命名空间名称重构代码,2) 防止缺乏冗长性,3) 提供与 API 无关的细节的抽象,同时 4) 提供与使用非内联命名空间相同的有益链接器诊断和行为。假设您正在使用一个库:
namespace library {
inline namespace abi_v1 {
class foo {
}
}
}
Run Code Online (Sandbox Code Playgroud)
它允许用户调用library::foo
时不需要知道或在文档中包含 ABI 版本,看起来更干净。使用起来library::abiverison129389123::foo
会显得很脏。
当更新时foo
,即向类添加新成员时,它不会影响 API 级别的现有程序,因为它们不会已经使用该成员,并且内联命名空间名称的更改不会更改 API 级别的任何内容因为library::foo
仍然有效。
namespace library {
inline namespace abi_v2 {
class foo {
//new member
}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,对于与其链接的程序,由于内联命名空间名称像常规命名空间一样被破坏为符号名称,因此更改对于链接器来说不会是透明的。因此,如果应用程序没有重新编译,而是与新版本的库链接,则会出现符号abi_v1
未找到错误,而不是实际链接,然后由于 ABI 不兼容而在运行时导致神秘的逻辑错误。添加新成员会因类型定义的更改而导致 ABI 兼容性,即使它在编译时(API 级别)不会影响程序。
在这种情况下:
namespace library {
namespace abi_v1 {
class foo {
}
}
inline namespace abi_v2 {
class foo {
//new member
}
}
}
Run Code Online (Sandbox Code Playgroud)
与使用 2 个非内联命名空间一样,它允许链接新版本的库,而无需重新编译应用程序,因为它将abi_v1
在全局符号之一中被破坏,并且它将使用正确的(旧的)类型定义。然而,重新编译应用程序会导致引用解析为library::abi_v2
.
usingusing namespace
的功能比 using 少inline
(因为超出范围的定义无法解析),但提供了与上面相同的 4 个优点。但真正的问题是,既然现在有一个专用关键字可以做到这一点,为什么还要继续使用解决方法呢?这是更好的实践,不那么冗长(必须更改 1 行代码而不是 2 行)并且使意图清晰。
我实际上发现了内联命名空间的另一种用途。
\n\n使用Qt,您可以使用 获得一些额外的、不错的功能Q_ENUM_NS
,这反过来又要求封闭的命名空间有一个元对象,该元对象是用 声明的Q_NAMESPACE
。但是,为了工作,同一文件中Q_ENUM_NS
必须有对应的Q_NAMESPACE
\xe2\x81\xbd\xc2\xb9\xe2\x81\xbe并且只能有一个,否则会出现重复定义错误。这实际上意味着所有枚举都必须位于同一标头中。恶心。
或者...您可以使用内联名称空间。将枚举隐藏在inline namespace
会导致元对象具有不同的损坏名称,同时向用户查找附加名称空间不存在\xe2\x81\xbd\xc2\xb2\xe2\x81\xbe。
因此,如果您出于某种原因需要这样做,它们对于将内容拆分为多个看起来像一个命名空间的子命名空间非常有用。当然,这与写using namespace inner
在外部命名空间类似,但没有DRY两次写入内部命名空间名称的
实际上比这更糟糕;它必须位于同一组大括号中。
除非您尝试在没有完全限定元对象的情况下访问它,但元对象几乎不会直接使用。
内联命名空间还可用于提供对命名空间内的功能/名称的细粒度访问。
这是用在std::literals
. std 中的命名空间literals
都是内联命名空间,因此:
using namespace std;
在某个地方使用,您还可以访问 std.h 中的所有用户定义的文字。using namespace std::literals::string_literals;
并且您将只获得在该命名空间中定义的 udl 符号。对于您想要访问非限定(udl、运算符等)的符号来说,这似乎是一种有用的技术,您可以将它们捆绑在一个内联命名空间中,这样您就可以在该(子)命名空间上进行特定的使用整个库的命名空间。
除了以上所有答案。
内联名称空间可用于编码符号中的ABI信息或功能的版本。由于这个原因,它们用于提供向后的ABI兼容性。内联名称空间使您可以将信息注入到整齐的名称(ABI)中,而无需更改API,因为它们仅影响链接器符号名称。
考虑以下示例:
假设您编写了一个函数Foo
,该函数采用对对象say的引用,但不bar
返回任何内容。
在main.cpp中说
struct bar;
void Foo(bar& ref);
Run Code Online (Sandbox Code Playgroud)
如果在将其编译为对象后检查该文件的符号名称。
$ nm main.o
T__ Z1fooRK6bar
Run Code Online (Sandbox Code Playgroud)
链接器符号名称可能会有所不同,但肯定会在某处对函数和参数类型的名称进行编码。
现在,可以将其bar
定义为:
struct bar{
int x;
#ifndef NDEBUG
int y;
#endif
};
Run Code Online (Sandbox Code Playgroud)
根据构建类型,bar
可以使用相同的链接器符号引用两种不同的类型/布局。
为了防止这种行为,我们将结构包装bar
到一个内联名称空间中,根据构建类型的bar
不同,链接器符号也将有所不同。
因此,我们可以这样写:
#ifndef NDEBUG
inline namespace rel {
#else
inline namespace dbg {
#endif
struct bar{
int x;
#ifndef NDEBUG
int y;
#endif
};
}
Run Code Online (Sandbox Code Playgroud)
现在,如果您查看每个对象的对象文件,则可以使用release构建一个对象,而使用debug标志构建另一个对象。您会发现链接器符号也包括内联名称空间名称。在这种情况下
$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar
Run Code Online (Sandbox Code Playgroud)
链接器符号名称可能不同。
注意符号名称中存在rel
和dbg
。
现在,如果您尝试将调试与发布模式链接,反之亦然,则将获得一个链接器错误,而不是运行时错误。