C++ inline内联命名空间的用法(附带实例)
C++11 标准引入了一种新的命名空间类型,称为内联命名空间。
内联命名空间本质上是一种机制,使得来自嵌套命名空间的声明就像是周围命名空间的一部分。使用 inline 关键字声明内联命名空间(匿名命名空间也可以内联)。
在本节中,我们将介绍如何使用内联命名空间来对符号进行版本控制。从这一节中,你将了解如何使用内联命名空间和条件编译来管理源代码版本。
接下来我们将讨论命名空间和嵌套命名空间、模板和模板特化,以及使用预处理器宏的条件编译。要学习本节内容,需要熟悉这些概念。
下面的例子展示了一个库,它有两个版本可供客户端使用:
为了更好地理解内联命名空间的作用,我们考虑这样一种情况:开发一个库,它会随着时间的推移从第一个版本发展到第二个版本(以及进一步发展),这个库在名为 modernlib 的命名空间下定义了所有的类型和函数。
在第一个版本中,这个库看起来像这样:
然而,客户端可能决定像下面这样特化模板函数 test():
到目前为止,一切运行正常,但是作为库开发人员,你决定创建库的第二个版本。但仍同时发布第一个和第二个版本,并通过宏让用户决定选择使用哪个版本。
在第二个版本中,你提供了 test() 函数的新实现,它不再返回 1,而是返回 2。为了能够同时提供第一个和第二个实现,你将它们放在名为 version_1 和 version_2 的嵌套命名空间中,并使用预处理器宏条件编译库:
这是因为 test 函数现在在一个嵌套的命名空间中,foo 的特化在 modernlib 命名空间中完成,但它实际上应该在 modernlib::version_1 或 modernlib::version_2 中实现。这是因为模板特化应在声明模板的同一命名空间中实现。
在这种情况下,客户端需要更改代码,如下所示:
有了 modernlib 库的这个定义,在 modernlib 命名空间中客户端代码定义的特化 test 函数就不再会被破坏,因为当模板特化完成时,version_1::test() 或 version_2::test()(取决于客户端用的版本)都是外围 modernlib 命名空间的一部分。实现细节现在对客户端是隐藏的,客户端只能看到外围命名空间 modernlib。
注意,命名空间 std 是为标准保留的,永远不应该内联。另外,如果命名空间在第一个定义中不是内联的,那么它就不应该被定义为内联的。
内联命名空间本质上是一种机制,使得来自嵌套命名空间的声明就像是周围命名空间的一部分。使用 inline 关键字声明内联命名空间(匿名命名空间也可以内联)。
在本节中,我们将介绍如何使用内联命名空间来对符号进行版本控制。从这一节中,你将了解如何使用内联命名空间和条件编译来管理源代码版本。
接下来我们将讨论命名空间和嵌套命名空间、模板和模板特化,以及使用预处理器宏的条件编译。要学习本节内容,需要熟悉这些概念。
C++ 内联命名空间的使用
要提供库的多个版本并让用户决定使用哪个版本,请执行以下操作:- 在命名空间内定义库的内容;
- 在内联命名空间中定义库的每个版本;
- 使用预处理器宏和 #if 指令来启用特定版本的库。
下面的例子展示了一个库,它有两个版本可供客户端使用:
namespace modernlib { #ifndef LIB_VERSION_2 inline namespace version_1 { template<typename T> int test(T value) { return 1; } } #endif #ifdef LIB_VERSION_2 inline namespace version_2 { template<typename T> int test(T value) { return 2; } } #endif }
C++ 内联命名空间工作原理
内联命名空间的成员被视为周围命名空间的成员,这样的成员可以偏特化、显式实例化或显式特化。这是一个传递属性,这意味着如果命名空间 A 包含内联命名空间 B,而 B 又包含内联命名空间 C,那么 C 的成员是 B 和 A 的成员,而 B 的成员是 A 的成员。为了更好地理解内联命名空间的作用,我们考虑这样一种情况:开发一个库,它会随着时间的推移从第一个版本发展到第二个版本(以及进一步发展),这个库在名为 modernlib 的命名空间下定义了所有的类型和函数。
在第一个版本中,这个库看起来像这样:
namespace modernlib { template<typename T> int test(T value) { return 1; } }库的客户端可以执行以下调用并返回值 1:
auto x = modernlib::test(42);
然而,客户端可能决定像下面这样特化模板函数 test():
struct foo { int a; }; namespace modernlib { template<> int test(foo value) { return value.a; } } auto y = modernlib::test(foo{ 42 });在本例中,y 的值不再是 1,而是 42,因为调用了用户特化的函数。
到目前为止,一切运行正常,但是作为库开发人员,你决定创建库的第二个版本。但仍同时发布第一个和第二个版本,并通过宏让用户决定选择使用哪个版本。
在第二个版本中,你提供了 test() 函数的新实现,它不再返回 1,而是返回 2。为了能够同时提供第一个和第二个实现,你将它们放在名为 version_1 和 version_2 的嵌套命名空间中,并使用预处理器宏条件编译库:
namespace modernlib { namespace version_1 { template<typename T> int test(T value) { return 1; } } #ifndef LIB_VERSION_2 using namespace version_1; #endif namespace version_2 { template<typename T> int test(T value) { return 2; } } #ifdef LIB_VERSION_2 using namespace version_2; #endif }出乎意料,客户端代码会崩溃,不管它使用的是库的第一个版本还是第二个版本。
这是因为 test 函数现在在一个嵌套的命名空间中,foo 的特化在 modernlib 命名空间中完成,但它实际上应该在 modernlib::version_1 或 modernlib::version_2 中实现。这是因为模板特化应在声明模板的同一命名空间中实现。
在这种情况下,客户端需要更改代码,如下所示:
#define LIB_VERSION_2 #include "modernlib.h" struct foo { int a; }; namespace modernlib { namespace version_2 { template<> int test(foo value) { return value.a; } } }这是一个问题,因为库暴露了实现细节,客户端需要了解这些细节以便进行模板特化。
有了 modernlib 库的这个定义,在 modernlib 命名空间中客户端代码定义的特化 test 函数就不再会被破坏,因为当模板特化完成时,version_1::test() 或 version_2::test()(取决于客户端用的版本)都是外围 modernlib 命名空间的一部分。实现细节现在对客户端是隐藏的,客户端只能看到外围命名空间 modernlib。
注意,命名空间 std 是为标准保留的,永远不应该内联。另外,如果命名空间在第一个定义中不是内联的,那么它就不应该被定义为内联的。