namespace一直是C++编程中封装性的重要概念,但是关于namespace的使用此前一直并没有太多总结,这里结合namespace的使用和C++11关于名字空间的新特性总结如下。
目录:
Sec1. 子命名空间间的信息交互
C++ 程序是编译器中的”符号表“结合不同的符号名修改规则来管理不同文件不同名字空间下的符号的,以防止出现符号冲突或未定义的问题。其逻辑很接近”字典“搜索,而命名空间是这一搜索链中重要的前端索引信息。
#include <iostream>
using namespace std;
namespace Jim {
namespace Basic {
struct Knife{ Knife() {cout << "Knife in Basic" << endl;} };
class CorkScrew{};
}
namespace Toolkit {
template<typename T> class SwissArmyKnife{};
}
//using namespace Basic; //若不在Other子名字空间定义之前显式打开Basic名字空间,则会导致下面变量b编译不通过。
namespace Other{
Knife b; //无法通过编译 error: Other子空间此时是无法得知Basic子空间的存在,故而Knife类型没有声明
struct Knife {Knife() {cout << "Knife in Other" << endl;} };
Knife c; //Knife in Other
Basic::Knife k; //Knife in Basic
}
using namespace Toolkit;
}
using namespace Jim;
int main()
{
Toolkit::SwissArmyKnife<Basic::Knife> sknife;
//使用C++98严格地名字空间的约束,这种同父空间下不同子名字空间中信息的交互显得极为僵硬
//按理来说,Jim的使用者不应该知道Jim中所有的子名字空间才能使用该库,但是上面这行代码要写出来,
//要求使用者得掌握Jim名字空间下所有的子名字空间,这不是一个优秀的库的编写方式
//搭配上面在Jim名字空间末尾的两处using,从而可以写出更简洁的代码
SwissArmyKnife<Knife> simpleKnife;
return 0;
}
Sec2. 不同名字空间下的模板类特化问题
上面的那种在Jim父空间末尾显式打开两个关键子空间的做法,显然是违背了封装性的原则,既然最后都是打开了,那么为什么一开始还写在子空间中,直接写在父空间下就可以了。但是上面的using
释放并非完全的释放,符号的使用还是存在子空间和父空间的细微差异的,其中一个便是模板类的特化问题
namespace Jim {
namespace Basic {
struct Knife{ Knife() {cout << "Knife in Basic" << endl;} };
class CorkScrew{};
}
namespace Toolkit {
template<typename T> class SwissArmyKnife{};
}
namespace Other{
Knife b; //无法通过编译 error: Other子空间此时是无法得知Basic子空间的存在,故而Knife类型没有声明
struct Knife {Knife() {cout << "Knife in Other" << endl;} };
Knife c; //Knife in Other
Basic::Knife k; //Knife in Basic
}
}
namespace Jim {
template<> class SwissArmyKnife<Knife> {};
//error: specialization of 'template<class T> class Jim::Toolkit::SwissArmyKnife' in different namespace [-fpermissive]|
//在不同的名字空间下特化模板类,即模板类定义空间和特化空间不是一个,哪怕属于父子空间的关系也不可以
}
这种情况下,为了追求完全的释放,并需要采用inline
关键字,即所谓的内联名字空间。
namespace Jim {
inline namespace Basic {
struct Knife{ Knife() {cout << "Knife in Basic" << endl;} };
class CorkScrew{};
}
inline namespace Toolkit {
template<typename T> class SwissArmyKnife{};
}
namespace Other{
Knife b; //编译通过,此时Knife就像定义在Jim父空间中
struct Knife {Knife() {cout << "Knife in Other" << endl;} };
Knife c; //Knife in Other
Basic::Knife k; //Knife in Basic
}
}
namespace Jim {
template<> class SwissArmyKnife<Knife> {}; //编译通过,inline关键字的引入,使得模板类的特化问题得到解决
}
Sec3. 终极一问
既然inline可以得到完全的释放,那么这种操作和直接将相关符号定义在父空间中有何区别呢?其实这便是inline namespace
使用场景的问题,如果是上面的代码,那么确实不如将所有的符号直接写在Jim父空间下,但是如果涉及到库的版本选择问题,则使用inline namespace
便很讲究了。
namespace Lib {
#if __cplusplus == 201103L
inline
#endif // __cplusplus
namespace cpp11{
struct func{ func() { cout << "Knife in c++11." << endl; } };
}
#if __cplusplus < 201103L
inline
#endif // __cplusplus
namespace oldcpp{
struct func{ func() { cout << "Knife in old cpp." << endl; } };
}
}
using namespace Lib;
int main()
{
func a; // Knife in c++ 11, 如果系统平台低一点,那么这里的func将会调用oldcpp名字空间中的,这个入口是默认的接口
cpp11::func b; // Knife in c++ 11
oldcpp::func c; // Knife in old cpp
return 0;
}
这样便可以根据系统的实际环境,内嵌不同的实现。编译器将根据当前系统环境选择合适的实现版本,从而保证默认接口都可以得以实现。但是如果直接显式调用子名字空间的方式也是可以的。inline namespace
归根接地还是和namespace
的初衷即“封装性”是违背的,故而除了上面这种存在编译器根据实际运行环境动态调整的情况下,其他场景慎用。