Answers:
内联名称空间是类似于符号版本控制的库版本控制功能,但是纯粹在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
根据的值__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
根据的值__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
这是完全有效的代码,其中用户为一组类型提供了自己的向量实现,而她显然知道比在STL(其副本)中找到的实现更有效的实现。
但是:专门化模板时,您需要在声明其的名称空间中进行操作。标准说,该vector
声明是在namespace中声明的std
,因此用户可以正确地期望对其进行专门化。
该代码与非版本化的名称空间std
或C ++ 11内联名称空间功能一起使用,但不适用于所使用的版本控制技巧using namespace <nested>
,因为该代码公开了实现细节,即在其中vector
定义的真实名称空间并非std
直接存在的。
还有其他漏洞可用来检测嵌套的名称空间(请参阅下面的注释),但是内联名称空间将它们全部塞住。这就是全部。对于将来非常有用,但是AFAIK标准没有为自己的标准库规定内联名称空间名称(不过,我很乐意证明这是错误的),因此它只能用于第三方库,不能用于标准本身(除非编译器供应商同意命名方案)。
std::cxx_11
。并非每一个编译器都会始终实现标准库的所有旧版本,尽管目前很容易想到,添加新版本时将现有实现留在旧版本中的负担很小。无论如何。我想该标准可以做的有用的事情是使其成为可选的,但是如果存在,则带有一个标准名称。
using namespace A
如果要查找,在名称空间B中,名称空间B中的名称将隐藏在名称空间A中的名称B::name
-内联名称空间则不是如此)。
ifdef
s用于完整的矢量实现?所有实现都将在一个命名空间中,但是在预处理之后将仅定义其中之一
using
关键字)来使用所需的任何实现。
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
名称空间调用最后一个版本吗?
using namespace Mine;
,并且Mine
名称空间包含内联名称空间中的所有内容Mine::V99
。
inline
从文件V99.h
中删除V100.h
。Mine.h
当然,您同时也要进行修改以添加额外的包含。Mine.h
是库的一部分,而不是客户端代码的一部分。
V100.h
,他们正在安装一个名为“ Mine”的库。还有在“地雷”的99版3个文件- Mine.h
,V98.h
和V99.h
。还有在“地雷”的100版本4个文件- ,,Mine.h
和。头文件的排列是与用户无关的实现细节。如果他们发现一些兼容性问题,这意味着他们需要专门使用部分或全部代码,则可以将对旧代码的调用与对新编写的代码的调用混合使用。V98.h
V99.h
V100.h
Mine::V98::f
Mine::V98::f
Mine::f
Mine
,而不必专门研究Mine::V99
或Mine::V98
。
除了所有其他答案。
内联名称空间可用于编码符号中的ABI信息或功能的版本。由于这个原因,它们被用来提供向后的ABI兼容性。内联名称空间使您可以将信息注入到整齐的名称(ABI)中,而无需更改API,因为它们仅影响链接器符号名称。
考虑以下示例:
假设您编写了一个函数Foo
,该函数引用对象的say语句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
};
}
现在,如果您查看每个对象的对象文件,则可以使用release构建一个对象,而使用debug标志构建另一个对象。您会发现链接器符号也包括内联名称空间名称。在这种情况下
$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar
链接器符号名称可能不同。
注意符号名称中存在rel
和dbg
。
现在,如果您尝试将调试与发布模式链接,反之亦然,则将获得一个链接器错误,而不是运行时错误。
实际上,我发现了内联名称空间的另一种用法。
借助Qt,您可以使用来获得一些额外的好功能Q_ENUM_NS
,这又要求封闭的名称空间具有一个元对象,该对象用声明Q_NAMESPACE
。但是,为了Q_ENUM_NS
工作,Q_NAMESPACE
在同一文件file中必须有一个对应的文件。而且只能有一个,否则会出现重复的定义错误。有效地,这意味着所有枚举都必须在同一标头中。uck
或者...您可以使用内联名称空间。将枚举隐藏在an中inline namespace
会导致元对象具有不同的名称,而像其他名称空间这样的用户查找对象并不存在。
因此,如果出于某种原因需要这样做,它们对于将内容拆分为多个看起来都像一个名称空间的子名称空间很有用。当然,这类似于using namespace inner
在外部名称空间中进行写入,但是不会两次写入内部名称空间的名称而导致DRY违反。
实际上比这更糟。它必须在同一组大括号中。
除非您尝试在不完全限定元对象的情况下访问它,否则几乎不会直接使用该元对象。
因此,要总结的要点,using namespace v99
并且inline namespace
是不一样的,前者是一个解决方法,以版本库专用的关键字(内联)在C ++ 11引入了固定用的问题之前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> {};
}
这将显示静态分析警告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
}
使B内联时,问题消失了。
inline
命名空间的其他功能是允许库编写者提供对库的透明更新: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
}
}
}
但是,对于与之链接的程序,由于内联名称空间名称像常规名称空间一样被改编为符号名称,因此更改对链接器而言不是透明的。因此,如果未重新编译应用程序,但是将其与库的新版本链接,它将显示一个符号。abi_v1
未找到错误,而不是实际链接,然后由于ABI不兼容而在运行时导致神秘的逻辑错误。添加新成员将由于类型定义的更改而导致ABI兼容性,即使它在编译时(API级别)不影响程序也是如此。
在这种情况下:
namespace library {
namespace abi_v1 {
class foo {
}
}
inline namespace abi_v2 {
class foo {
//new member
}
}
}
像使用2个非内联名称空间一样,它允许链接库的新版本,而无需重新编译应用程序,因为abi_v1
它将在全局符号之一中被修饰并且将使用正确的(旧)类型定义。但是,重新编译应用程序将导致引用解析为library::abi_v2
。
与使用using namespace
相比,使用的功能要少inline
(因为无法定义行定义),但提供了与上述相同的4个优点。但是真正的问题是,当现在有专用关键字来执行此操作时,为什么要继续使用替代方法。这是一种更好的做法,不那么冗长(必须更改1行代码而不是2行),并且使意图清晰明了。
using namespace V99;
在Stroustrup的示例中不起作用。