场景需求
在早期C项目中,我们常常会因为命名冲突问题需要有一个“字典”来存储所用过的变量。这是因为在C语言中非静态全局变量、函数都是全局共享的。
C++就通过命名空间(也叫名字空间)来解决C语言中这个头疼的问题。实现分割全局共享的命名空间。程序员在编写代码时可以自己设置命名空间,使用者只需要通过空间名::函数/变量或者using namespace 空间名就可以使用(推荐使用前者方法)。但是,当我们空间名嵌套多层时在使用上不是很方便。
命名空间嵌套的弊端
在下面这段代码中,用户1将他的代码封装为LINXI
,然后内部又进行了细分为BB
、CC
、DD
。而且在DD
空间下使用了BB
的类型。
#include <iostream> using namespace std; namespace LINXI { namespace BB { class T1 { public: T1() { cout << "T1 is BB" << endl; } }; } namespace CC { template<class T> class T2 { }; } namespace DD { BB::T1 t1; class T1 { public: T1() { cout << "T1 in DD" << endl; } }; T1 t2; BB::T1 t3; } } int main() { LINXI::CC::T2<LINXI::BB::T1> t; return 0; }
那么当我们需要创建一个LINXI空间下的CC
的模板类时,且类型为BB
的T1
类型,那么代码将变得过于臃肿且晦涩难懂。
所以为了解决这种实际生产环境中命名空间的嵌套,导致使用上会有一定的不便。请接着往下看。
内联名字空间(inline namespace)
在C++11中引入了内联命名空间,可以通过inline namespace
声明一个内联的命名空间。
作用:内联命名空间可以让程序员在父命名空间定义或特化子命名空间的模板。
通过内联命名空间优化嵌套问题
我们只需要在之前的基础上将BB子命名空间和CC子命名空间改成内联命名空间,然后我们就可以很简单的实现上述的操作。
#include <iostream> using namespace std; namespace LINXI { inline namespace BB { class T1 { public: T1() { cout << "T1 is BB" << endl; } }; class T3 {}; } inline namespace CC { template<class T> class T2 {}; } namespace DD { T1 b; // T1 is BB struct T1 { T1() { cout << "T1 is DD" << endl; } }; T1 t1; // T1 is DD BB::T1 t2; // T1 is BB } }
当我们使用特例化时就需要在LINXI的命名空间下进行实现。
namespace LINXI { template<> class T2<T1>{}; // 特例化 }
创建一个T1
模板的类T2
的对象我们可以通过下面俩种方法实现:
using namespace LINXI; T2<T1> t;
LINXI::T2<LINXI::T1> t;
运行结果:
T1 is BB T1 is DD T1 is BB
这也有缺点会使BB
的命名空间形如虚设,使得命名空间的分割性就失去了。
内联命名空间配合宏使用
在下面这段代码中,LINXI
命名空间内还有cpp11
、cpp14
、cpp
命名空间,这里我们使用到了__cplusplus
C++的版本宏,如果当前的版本与宏的关系成立那么就将该命名空间内联到LINXI
中。
#include <iostream> using namespace std; // 201103L(C++11) 201402L(C++14), 201703L(C++17), or 202002L(C++20) namespace LINXI { #if __cplusplus == 201103L inline #endif namespace cpp11 { class AA { public: AA() { cout << "AA is C++11" << endl; } }; } #if __cplusplus == 201402L inline #endif namespace cpp14 { class AA { public: AA() { cout << "AA is C++14" << endl; } }; } #if __cplusplus < 201103L inline #endif namespace cpp { class AA { public: AA() { cout << "AA is C++" << endl; } }; } } int main() { using namespace LINXI; AA a; // 默认版本 C++ cpp11::AA a11; // 强制使用C++11 cpp14::AA a14; // 强制使用C++14 return 0; }
运行结果
AA is C++ AA is C++11 AA is C++14
**优点:**对于长期需要维护的项目,在版本迭代更新时非常的方便。
ADL特性(Argument-Dependent name Lookup)
作用:ADL是允许编译器在命名空间内找不到函数名称时,会在参数的命名空间中继续进行查找函数命名。
下面这段代码中,函数ADLFunction
就不需要在使用中声明自己的命名空间,因为编译器可以在参数a
的命名空间中找到ADL
,编译器也就可以成功识别了。
#include <iostream> using namespace std; namespace ADL { struct A{}; void ADLFunction(A a); } int main() { ADL::A a; ADLFunction(a); return 0; }
可以看到我们没有写完参数时,编译器一直提醒我们添加ADL
命名空间。
在写完之后,编译器已经可以识别这个函数了。并且可以正常运行。
总结
ADL带来了一定的便捷性,但也破坏了命名空间的封装性,而且更多人觉得ADL特性缺大于优,比较鸡肋。所以我们还是在使用时还是通过::
方式吧。