前言
最近看到个问题, 就是在命名空间中声明一个变量 ( int rand = 0 ), 用using namespace将这个命名空间引入 ( 污染
) 进全局空间, 当函数调用此变量时发生错误.
这是命名空间全局污染典型案例, 我们进行一些剖析
一、命名空间是什么?
C++ 命名空间(namespace)是一种将全局作用域分割为若干个小的作用域的机制。
它可以解决命名冲突(name clash)的问题,使不同作用域下的同名标识符互不干扰。
命名空间的使用方法如下:
1.定义命名空间:可以在全局空间下定义命名空间,也可以在已有的命名空间内定义子命名空间,例如:
namespace math
{
int add(int a, int b)
{
return a + b;
}
namespace geometry
{
double circle_area(double r)
{
return 3.14159 * r * r;
}
} // namespace geometry
} // namespace math
2.使用命名空间:可以用 using namespace
或 using
关键字引入一个或多个命名空间,也可以使用作用域限定符访问命名空间中的标识符。
using namespace math; // 引入 math 命名空间
using math::geometry::circle_area; // 仅引入 math::geometry::circle_area 标识符
int main()
{
int sum = math::add(1, 2); // 使用作用域限定符访问 math 命名空间中的函数
double area = circle_area(10.0); // 直接使用 circle_area 标识符(已引入)
return 0;
}
二、命名空间全局污染
命名空间是一种防止变量和函数因名称重复, 导致程序失败的机制。
全局污染是指太多的变量和函数被定义在全局命名空间中,容易产生命名冲突和互相干扰,从而导致程序出现错误。
通过使用命名空间,可以将变量和函数封装到特定的命名空间中,以避免与其他变量和函数冲突。
这提高了代码的可维护性和可读性,并使代码更易于理解和修改。
但是, 有一种不良的编程习惯, 就是将命名空间引入全局, 导致全局污染
的发生.
以下是一个示例
#include
namespace hk
{
int rand = 0;
}
using namespace hk;
auto main() -> int
{
auto a = rand;
}
代码中, hk作为一个命名空间, 被引入到全局, 然后我们惊讶的发现, 程序中的 rand 发生了冲突, 编译器无法识别, rand究竟是一个int对象还是一个函数指针.
仔细剖析, 发现, rand本身是一个库函数, 当 hk 污染进全局, 作为int对象的 rand 和 函数名 rand 冲突了.
只是我们没有引入过与rand函数相关的头文件, 这个rand又是哪里来的?
这就是C++的要命之处, 头文件的相互引用.
我们只是引入
, 而它又引用其他头文件, 其他头文件不知引用了多少次之后, 引用了
头文件, 是的, 你无法知道你的实现代码中究竟引用了什么.
这也是为什么, C++的前辈告诉我们, 永远不要使用using namespace
将命名空间引入全局的原因, 这与命名空间的初衷背道而驰, 它是用来解决命名冲突问题的, 不是引入命名冲突问题的.
所以, 在偷懒的时候, 一定要知道偷懒可能产生的代价.
为了解决上述问题, 代码不得不改为:
auto main() -> int
{
auto a = hk::rand;
auto b = ::rand;
}
总结
有时候, 前人总结的经验不一定立刻起到作用, 比如, 绝对不要人为引入全局空间污染
,
但当你违背以上警告, 却没有产生问题, 还貌似提高了打字效率,
殊不知, 各种莫名奇妙的bug就在路上等着, 你可能害了整个团队, 让一个下游程序员莫名奇妙的追踪数百乃至数万个编译报错
, 蹂躏并撕扯下他本就所剩无几的头发.