首页 > 编程笔记 > C++笔记 阅读:40

C++ inline内联命名空间的用法(附带实例)

C++11 标准引入了一种新的命名空间类型,称为内联命名空间。

内联命名空间本质上是一种机制,使得来自嵌套命名空间的声明就像是周围命名空间的一部分。使用 inline 关键字声明内联命名空间(匿名命名空间也可以内联)。

在本节中,我们将介绍如何使用内联命名空间来对符号进行版本控制。从这一节中,你将了解如何使用内联命名空间和条件编译来管理源代码版本。

接下来我们将讨论命名空间和嵌套命名空间、模板和模板特化,以及使用预处理器宏的条件编译。要学习本节内容,需要熟悉这些概念。

C++ 内联命名空间的使用

要提供库的多个版本并让用户决定使用哪个版本,请执行以下操作:
下面的例子展示了一个库,它有两个版本可供客户端使用:
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 是为标准保留的,永远不应该内联。另外,如果命名空间在第一个定义中不是内联的,那么它就不应该被定义为内联的。

相关文章