C++ 内联命名空间有什么用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11016220/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are inline namespaces for?
提问by Walter
C++11 allows inline namespace
s, all members of which are also automatically in the enclosing namespace
. I cannot think of any useful application of this -- can somebody please give a brief, succinct example of a situation where an inline namespace
is needed and where it is the most idiomatic solution?
C++11 允许inline namespace
s,其所有成员也自动包含在封闭的namespace
. 我想不出任何有用的应用 - 有人可以举一个简短的例子inline namespace
来说明需要an的情况以及它是最惯用的解决方案吗?
(Also, it is not clear to me what happens when a namespace
is declared inline
in one but not all declarations, which may live in different files. Isn't this begging for trouble?)
(另外,当发生了什么并不清楚,我namespace
声明inline
在一个但不是所有的声明,这可能住在不同的文件。这难道不是找麻烦?)
回答by Marc Mutz - mmutz
Inline namespaces are a library versioning feature akin to symbol versioning, but implemented purely at the C++11 level (ie. cross-platform) instead of being a feature of a specific binary executable format (ie. platform-specific).
内联命名空间是类似于符号版本控制的库版本控制功能,但纯粹在 C++11 级别(即跨平台)实现,而不是特定二进制可执行格式的功能(即特定于平台)。
It is a mechanism by which a library author can make a nested namespace look and act as if all its declarations were in the surrounding namespace (inline namespaces can be nested, so "more-nested" names percolate up all the way to the first non-inline namespace and look and act as if their declarations were in any of the namespaces in between, too).
它是一种机制,库作者可以通过这种机制使嵌套的命名空间看起来和行为好像它的所有声明都在周围的命名空间中(内联命名空间可以嵌套,因此“更多嵌套”的名称一直渗透到第一个非-inline 命名空间,并且看起来和行为好像它们的声明也位于两者之间的任何命名空间中)。
As an example, consider the STL implementation of vector
. If we had inline namespaces from the beginning of C++, then in C++98 the header <vector>
might have looked like this:
例如,考虑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
Depending on the value of __cplusplus
, either one or the other vector
implementation is chosen. If your codebase was written in pre-C++98 times, and you find that the C++98 version of vector
is causing trouble for you when you upgrade your compiler, "all" you have to do is to find the references to std::vector
in your codebase and replace them by std::pre_cxx_1997::vector
.
根据 的值__cplusplus
,vector
选择一个或另一个实现。如果你的代码写在预C ++ 98次,你会发现C ++ 98版本的vector
作祟你,当你升级你的编译器,“所有”,你需要做的就是找到引用std::vector
在您的代码库并将它们替换为std::pre_cxx_1997::vector
.
Come the next standard, and the STL vendor just repeats the procedure again, introducing a new namespace for std::vector
with emplace_back
support (which requires C++11) and inlining that one iff __cplusplus == 201103L
.
来到下一个标准,STL 供应商只是再次重复该过程,为std::vector
withemplace_back
支持(需要 C++11)引入一个新的命名空间并内联该命名空间iff __cplusplus == 201103L
。
OK, so why do I need a new language feature for this? I can already do the following to have the same effect, no?
好的,那么为什么我需要一个新的语言功能呢?我已经可以执行以下操作以获得相同的效果,不是吗?
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
Depending on the value of __cplusplus
, I get either one or the other of the implementations.
根据 的值__cplusplus
,我得到一个或另一个实现。
And you'd be almost correct.
你几乎是正确的。
Consider the following valid C++98 user code (it was permitted to fully specialize templates that live in namespace std
in C++98 already):
考虑以下有效的 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
This is perfectly valid code where the user supplies its own implementation of a vector for a set of type where she apparently knows a more efficient implementation than the one found in (her copy of) the STL.
这是完全有效的代码,其中用户为一组类型提供自己的向量实现,其中她显然知道比(她的)STL 中找到的实现更有效的实现。
But: When specializing a template, you need to do so in the namespace it was declared in. The Standard says that vector
is declared in namespace std
, so that's where the user rightfully expects to specialize the type.
但是:当专门化一个模板时,你需要在它被声明的命名空间中这样做。标准说这vector
是在 namespace 中声明的std
,所以这是用户理所当然地期望专门化类型的地方。
This code works with a non-versioned namespace std
, or with the C++11 inline namespace feature, but not with the versioning trick that used using namespace <nested>
, because that exposes the implementation detail that the true namespace in which vector
was defined was not std
directly.
此代码适用于非版本化的命名空间std
,或 C++11 内联命名空间功能,但不适用于使用的版本控制技巧using namespace <nested>
,因为这暴露了vector
定义的真正命名空间不是std
直接的实现细节。
There are other holes by which you could detect the nested namespace (see comments below), but inline namespaces plug them all. And that's all there is to it. Immensely useful for the future, but AFAIK the Standard doesn't prescribe inline namespace names for its own standard library (I'd love to be proven wrong on this, though), so it can only be used for third-party libraries, not the standard itself (unless the compiler vendors agree on a naming scheme).
您还可以通过其他漏洞检测嵌套命名空间(请参阅下面的注释),但内联命名空间将它们全部插入。这就是全部。对未来非常有用,但 AFAIK 标准并没有为其自己的标准库规定内联命名空间名称(不过,我很想证明这是错误的),因此它只能用于第三方库,而不是标准本身(除非编译器供应商就命名方案达成一致)。
回答by Steve Jessop
http://www.stroustrup.com/C++11FAQ.html#inline-namespace(a document written by and maintained by Bjarne Stroustrup, who you'd think should be aware of most motivations for most C++11 features.)
http://www.stroustrup.com/C++11FAQ.html#inline-namespace(由 Bjarne Stroustrup 编写和维护的文档,您认为他应该了解大多数 C++11 功能的大部分动机。 )
According to that, it is to allow versioning for backward-compatibility. You define multiple inner namespaces, and make the most recent one inline
. Or anyway, the default one for people who don't care about versioning. I suppose the most recent one could be a future or cutting-edge version which is not yet default.
据此,它允许版本控制以实现向后兼容性。您定义多个内部命名空间,并创建最新的一个inline
。或者无论如何,对于不关心版本控制的人来说,这是默认的。我想最新的版本可能是尚未默认的未来版本或尖端版本。
The example given is:
给出的例子是:
// 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
I don't immediately see why you don't put using namespace V99;
inside namespace Mine
, but I don't have to entirely understand the use-case in order to take Bjarne's word for it on the committee's motivation.
我没有立即明白你为什么不把using namespace V99;
内命名空间Mine
,但我不必完全理解用例,以便在委员会的动机上接受 Bjarne 的话。
回答by coder3101
In addition to all the other answers.
除了所有其他答案。
Inline namespace can be used to encode ABI information or Version of the functions in the symbols. It is due to this reason they are used to provide backward ABI compatibility. Inline namespaces let you inject information into the mangled name (ABI) without altering the API because they affect linker symbol name only.
内联命名空间可用于对符号中的 ABI 信息或函数版本进行编码。正是由于这个原因,它们被用来提供向后 ABI 兼容性。内联命名空间让您可以在不改变 API 的情况下将信息注入到重整名称 (ABI) 中,因为它们仅影响链接器符号名称。
Consider this example:
考虑这个例子:
Suppose you write a function Foo
that takes a reference to an object say bar
and returns nothing.
假设您编写了一个函数Foo
,该函数引用对象 saybar
并且不返回任何内容。
Say in main.cpp
在 main.cpp 中说
struct bar;
void Foo(bar& ref);
If you check your symbol name for this file after compiling it into an object.
如果在将其编译为对象后检查该文件的符号名称。
$ nm main.o
T__ Z1fooRK6bar
The linker symbol name may vary but it will surely encode the name of function and argument types somewhere.
链接器符号名称可能会有所不同,但它肯定会在某处对函数和参数类型的名称进行编码。
Now, it could be that bar
is defined as:
现在,它可能被bar
定义为:
struct bar{
int x;
#ifndef NDEBUG
int y;
#endif
};
Depending upon Build type, bar
can refer to two different types/layouts with same linker symbols.
根据构建类型,bar
可以使用相同的链接器符号引用两种不同的类型/布局。
To prevent such behavior we wrap our struct bar
into an inline namespace, where depending upon the Build type the linker symbol of bar
will be different.
为了防止这种行为,我们将我们的结构包装bar
到一个内联命名空间中,其中根据构建类型的bar
不同,链接器符号会有所不同。
So, we could write:
所以,我们可以这样写:
#ifndef NDEBUG
inline namespace rel {
#else
inline namespace dbg {
#endif
struct bar{
int x;
#ifndef NDEBUG
int y;
#endif
};
}
Now if you look at the object file of each object you build one using release and other with debug flag. You will find that linker symbols include inline namespace name as well. In this case
现在,如果您查看每个对象的目标文件,您将使用 release 构建一个对象文件,并使用调试标志构建另一个对象文件。您会发现链接器符号也包含内联命名空间名称。在这种情况下
$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar
Linker Symbol names may be different.
链接器符号名称可能不同。
Notice presence of rel
and dbg
in the symbol names.
通知存在rel
,并dbg
在符号名。
Now, if you try to link debug with release mode or vise-versa you will get a linker error as contrary to runtime error.
现在,如果您尝试将调试链接到发布模式或反之亦然,您将收到与运行时错误相反的链接器错误。
回答by Matthew
I actually discovered another use for inline namespaces.
我实际上发现了内联命名空间的另一种用途。
With Qt, you get some extra, nice features using Q_ENUM_NS
, which in turn requires that the enclosing namespace has a meta-object, which is declared with Q_NAMESPACE
. However, in order for Q_ENUM_NS
to work, there has to be a corresponding Q_NAMESPACE
in the same file?1?. And there can only be one, or you get duplicate definition errors. This, effectively, means that all of your enumerations have to be in the same header. Yuck.
使用Qt,您可以使用 获得一些额外的、不错的功能Q_ENUM_NS
,这反过来又要求封闭的命名空间有一个元对象,它用 声明Q_NAMESPACE
。然而,为了Q_ENUM_NS
工作,Q_NAMESPACE
在同一个文件中必须有对应的?1?。并且只能有一个,否则会出现重复的定义错误。这实际上意味着您的所有枚举都必须在同一个标题中。哎呀。
Or...you can use inline namespaces. Hiding enumerations in an inline namespace
causes the meta-objects to have different mangled names, while looking to users like the additional namespace doesn't exist?2?.
或者...您可以使用内联命名空间。在一个中隐藏枚举inline namespace
会导致元对象具有不同的重整名称,而在用户看来,附加命名空间不存在?2?。
So, they're useful for splitting stuff into multiple sub-namespaces that all looklike one namespace, if you need to do that for some reason. Of course, this is similar to writing using namespace inner
in the outer namespace, but without the DRYviolation of writing the name of the inner namespace twice.
因此,如果出于某种原因需要这样做,它们对于将内容拆分为多个子命名空间很有用,这些子命名空间看起来都像一个命名空间。当然,这类似于using namespace inner
在外部命名空间中写入,但没有两次写入内部命名空间的名称的DRY冲突。
It's actually worse than that; it has to be in the same set of braces.
Unless you try to access the meta-object without fully qualifying it, but the meta-object is hardly ever used directly.
实际上比这更糟;它必须在同一组大括号中。
除非您尝试在没有完全限定的情况下访问元对象,否则元对象几乎不会被直接使用。
回答by Lewis Kelsey
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).
所以总结一下要点,using namespace v99
并且inline namespace
不一样,前者是在 C++11 中引入专用关键字(内联)之前版本库的一种解决方法,它修复了 using 的问题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> {};
}
This will show a static analysis warning first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
. But if you make namespace A inline, then the compiler correctly resolves the specialisation. Although, with the C++11 extensions, the issue goes away.
这将显示静态分析警告first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
。但是,如果您将命名空间 A 内联,则编译器会正确解析特化。尽管使用 C++11 扩展,问题消失了。
Out-of-line definitions don't resolve when using using
; they have to be declared in a nested/non-nested extension namespace block(which means the user needs to know the ABI version again, if for whatever reason they were permitted to provide their own implementation of a function).
使用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
}
The issue goes away when making B inline.
使 B 内联时,问题就消失了。
The other functionality inline
namespaces have is allowing the library writer to provide a transparent update to the library 1) without forcing the user to refactor code with the new namespace name and 2) preventing lack of verbosity and 3) providing abstraction of API-irrelevant details, whilst 4) giving the same beneficial linker diagnostics and behaviour that using a non-inline namespace would provide. Let's say you're using a library:
inline
命名空间的其他功能是允许库编写者向库提供透明更新 1) 不强迫用户使用新的命名空间名称重构代码 2) 防止缺乏冗长和 3) 提供 API 无关细节的抽象,同时 4) 提供与使用非内联命名空间相同的有益链接器诊断和行为。假设您正在使用一个库:
namespace library {
inline namespace abi_v1 {
class foo {
}
}
}
It allows the user to call library::foo
without needing to know or include the ABI version in the documentation, which looks cleaner. Using library::abiverison129389123::foo
would look dirty.
它允许用户调用library::foo
而无需知道或在文档中包含 ABI 版本,这看起来更清晰。使用library::abiverison129389123::foo
会看起来很脏。
When an update is made to foo
, i.e. adding a new member to the class, it will not affect existing programs at the API level because they wont already be using the member AND the change in the inline namespace name will not change anything at the API level because library::foo
will still work.
对 进行更新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).
但是,对于与其链接的程序,因为内联命名空间名称像常规命名空间一样被分解为符号名称,因此更改对链接器来说是不透明的。因此,如果应用程序没有被重新编译,而是与新版本的库链接,它会出现一个符号abi_v1
未找到错误,而不是实际链接然后在运行时由于 ABI 不兼容而导致神秘的逻辑错误。添加新成员会由于类型定义的变化而导致 ABI 兼容性,即使它在编译时(API 级别)不会影响程序。
In this scenario:
在这种情况下:
namespace library {
namespace abi_v1 {
class foo {
}
}
inline namespace abi_v2 {
class foo {
//new member
}
}
}
Like using 2 non-inline namespaces, it allows for a new version of the library to be linked without needing to recompile the application, because abi_v1
will be mangled in one of the global symbols and it will use the correct (old) type definition. Recompiling the application would however cause the references to resolve to library::abi_v2
.
就像使用 2 个非内联命名空间一样,它允许链接新版本的库而无需重新编译应用程序,因为abi_v1
它将在全局符号之一中被破坏并且它将使用正确的(旧)类型定义。但是,重新编译应用程序会导致引用解析为library::abi_v2
.
Using using namespace
is less functional than using inline
(in that out of line definitions don't resolve) but provides the same 4 advantages as above. But the real question is, why continue to use a workaround when there is now a dedicated keyword to do it. It's better practice, less verbose (have to change 1 line of code instead of 2) and makes the intention clear.
Usingusing namespace
的功能不如 using inline
(因为行外定义无法解析),但提供了与上述相同的 4 个优点。但真正的问题是,当现在有专用关键字来执行此操作时,为什么还要继续使用解决方法。这是更好的实践,不那么冗长(必须更改 1 行代码而不是 2 行)并使意图清晰。