【C++】C++11新特性之内联的命名空间(inline namespace)及ADL特性(Argument-Dependent name Lookup)

今天看代码看到一个inline namespace:

#if ABSL_OPTION_USE_INLINE_NAMESPACE == 0
#define ABSL_NAMESPACE_BEGIN
#define ABSL_NAMESPACE_END
#elif ABSL_OPTION_USE_INLINE_NAMESPACE == 1
#define ABSL_NAMESPACE_BEGIN \
  inline namespace ABSL_OPTION_INLINE_NAMESPACE_NAME {
#define ABSL_NAMESPACE_END }
#else
#error options.h is misconfigured.
#endif

C++11标准中,可以在父命名空间中定义内联的子命名空间,内联的子命名空间可以把其包含的名字导入到父命名空间中,从而在父命名空间中可以直接访问子命名空间中定义的名字,而不用通过域限定符Child::name的形式来访问。例如下面的代码:

namespace Parent
{
    namespace Child1
    {
        struct child1_data{int a;} ;
    }
    namespace Child2
    {
        struct child2_data{int b;} ;
    }
    namespace child3
    {
        child1_data data1;   //编译失败
        child2_data data2;   //编译失败
    }
}

编译结果为:

:g++ main.cc -o main
main.cc:25:9: error: unknown type name 'child1_data'; did you mean 'Child1::child1_data'?
        child1_data data1;   //编译失败
        ^~~~~~~~~~~
        Child1::child1_data
main.cc:17:16: note: 'Child1::child1_data' declared here
        struct child1_data{int a;} ;
               ^
main.cc:26:9: error: unknown type name 'child2_data'; did you mean 'Child2::child2_data'?
        child2_data data2;   //编译失败
        ^~~~~~~~~~~
        Child2::child2_data
main.cc:21:16: note: 'Child2::child2_data' declared here
        struct child2_data{int b;} ;
               ^
2 errors generated.

在命名空间child3中,引用了命名空间Child1和Child2中的名字,由于C++98中不允许跨命名空间的直接使用名字,因而导致编译失败。为了解决这个问题,C++11标准引入了内联的命名空间(inline namespace),下面的代码就可以顺利编译:

namespace Parent
{
    inline namespace Child1
    {
        struct child1_data{int a;} ;
    }
    inline namespace Child2
    {
        struct child2_data{int b;} ;
    }
    namespace child3
    {
        child1_data data1;   //编译失败
        child2_data data2;   //编译失败
    }
}

编译结果为:

:g++ main.cc -o main
main.cc:15:5: warning: inline namespaces are a C++11 feature [-Wc++11-inline-namespace]
    inline namespace Child1
    ^
main.cc:19:5: warning: inline namespaces are a C++11 feature [-Wc++11-inline-namespace]
    inline namespace Child2
    ^
2 warnings generated.

注意:匿名的命名空间也可一把其包含的名字导入到父命名空间,但是匿名的命名空间(与使用using打开命名空间一样)无法支持在父命名空间中对匿名的命名口空间中所包含的模板函数进行特化。

缺点:内联的命名空间会破坏命名空间的封装性。

严肃的问题

前面提到内联命名空间就像不存在一样,那么就产生了一个严肃的问题:它有什么用?为了回答这个问题,我们举一个更加接近实际开发的例子。假设有如下类库代码:

class A {
public:
    void fun(){}
};

class B {
public:
    void fun(){}
};

void inline_demo() {
    A a;
    a.fun();
    B b;
    b.fun();
}

到这里为止算是铺垫,接下来才是重点:假设我们队类库进行了升级,同时又希望:

  1. 使用者代码不受影响,除非使用者自己想改

  2. 可以自由使用新类库的功能

  3. 如果有需要仍然可以使用原来的类库

解决方法当然是使用内联命名空间。首先是对类库进行处理:

namespace ver1 {
class A {
public:
    void fun(){}
};
}

namespace ver1 {
class B {
public:
    void fun(){}
};
}

inline namespace ver2 {
class A {
public:
    void fun_2(){}
};
}

namespace ver2 {
class B {
public:
    void fun(){}
};
class C {
public:
    void fun_3(){}
};
}

代码中为每个版本的类库定义了命名空间,同时将最新版本定义为内联命名空间。有了这样的准备之后,使用者代码可以是下面这样:

void inline_demo2() {
    A a;
    a.fun_2();
    B b;
    b.fun();
    C c;
    c.fun_3();
    
    ver1::A al;
    al.fun()
}

使用最新类库的时候,就好像没有定义过命名空间一样;如果实在是需要原来的类库,可以通用版本前缀加类名的方式。

还有一点很重要:由于隐式内联语法的存在,将来出现ver3的时候,只要将唯一的一个inline关键字移动到第一次出现的ver3定义之前就可以了!

ADL特性(Argument-Dependent name Lookup)

ADL 是C++的一个语言特性,即“参数关联名称查找”,是指当编译器在名字空间找不到一个函数的定义时,就会到这个函数的参数所在的命名空间去查找,比如下面的例子:


#include <iostream>
using namespace std;
 
namespace name_ADL
{
    struct St{} ;
    void ADLfunction(St s){}
    
}
int main()
{
   name_ADL::St st ;
   ADLfunction(st) ; //注意,此处没有用域限定符,但仍然可通过编译
   return 0;
}

在使用函数ADLfunction时,无需使用其所在的命名空间,因为编译器会去函数参数st所在的命名空间去查找,所以编译器不会报错。由此可见,ADL特性虽然在使用上带来了以下方便,但是却破坏了命名空间原有的封装性。因此,我们在使用名字的时候,最好先用using打开命名空间,或者使用“::”列出函数或变量的完整命名空间。

发布了423 篇原创文章 · 获赞 14 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/LU_ZHAO/article/details/105475153
今日推荐