引言
C 的全局性名称空间容易生成名称冲突的问题。
想法1:名称属于编译单元,需要使用 extern
来公开。 失败!
想法2:由类型安全的连接机制: extern "C" { /*..*/ }
想到 extern XXX { /*...*、 }
,要从其他作用域需要使用 XXX::
的访问形式。由于时间关系没有跟进。
提案1: 建议采用记法: bundle XXX { /*...*/}
。
提案2: 采用 :: XXX :: { /*...*/ }
问题
在 C 中常见的一些解决名称冲突的办法是在名称前面加前缀。
有人使用宏机制来加前缀 。
#define my(X) myprefix_##X
char my(f)(char);
另一种方式是使用一个类包裹。
解决方案的思想
- 能够在连接两个库时不出现名称冲突。
- 能够在引进新的名称时不必担心与别人已经有的名称冲突。
- 能够在库的实现里增加一个名称而又不影响库的用户。
- 能够从两个不同的库中选择名称,即使这两个库恰好使用了同样的名称。
- 能够不修改有关的函数本身,而消解掉出现的名称冲突。
- 增加新名称,不会影响其他名称空间的代码。
- 能够避免名称空间的名称之间发生冲突。
- 有处理标准库的能力。
- 与 C 和 C++兼容。
- 不增加任何连接或运行时的额外成本。
- 不增加比使用全局名称的用户更多描述上的麻烦。
- 使人能够在使用名称的代码中显式指明某个名称的假定出处。
一个解决方案: 名称空间
- 一种定义作用域机制,即名称空间,用于放置传统 C 和 C++ 全局声明。这种作用域可以命名,访问名称空间成员采用访问类成员的传统记法:
namespace_name::member_name
。 事实上,类作用域可以看成是名称空间使用域的一种特殊情况。 - 一种为名称空间定义局部同义词的机制。
- 一种允许不显示写出
namespace_name::
限定词而访问名称空间成员的机制:使用声明。 - 一种允许不显示写出
namespace_name::
限定词而直接访问名称空间的全部成员的机制: 使用指示。
以下面的名称空间声明为例:
namespace A{
void f(int);
void f(char);
class String { /*...*/ };
// ...
}
- 通过显式限定直接使用:
A::String s1 = "Anne";
void g1(){
A::f(1);
}
- 导入个别名称:
using A::String;
String s2 = "Nicholas";
void g2(){
using A::f;
f(2);
}
- 全部导入:
using namespace A;
String s3 = "Marian";
void g3(){
f(3);
}
上面的三种方式是在多种观点中长时间讨论的结果。
使用名称空间投入使用
- 对于原来的 C 标准库,可以通过使用名称空间+使用指示是一种好的解决方案。如:
// stdio.h
namespace std{
int printf(const char * ...);
}
using namespace std;
使用指示可以认为是一种转变工具,来自本名称空间不需要限定,这方面的行为跟类完全一样。
名称空间的别名。
如 namespace ATT = American_Telephone_and_Telegraph;
这个特征还允许用户引用 “某个库” 而不必准确说出每次实际上使用的是哪个库。也可以用来组合多个名称空间。
利用名称空间管理版本问题
// lib.h 旧版本
namespace lib = release1;
// 新版本
namespace lib =release2;
其他细节
- 使用声明向局部作用域添加一些内容,使用指示则不添加任何内容,只是使一些名称能够被访问,例如:
namespace X {
int i,j,k;
}
int k;
void f1(){
int i = 0;
using namespace X;
i++;
j++;
k++;// error: X::k or global k ?
::k++;
X::k++;
}
void f2(){
int i = 0;
using X::i; // error: Target of using declaration conflicts with declaration already in scope
using X::j;
using X::k;
i++;
j++;
k++;
}
特别地,与使用指示有关的错误都只在使用点检查。这样不会因为潜在的失败造成程序失败。示例如下:
namespace A {
int x;
}
namespace B {
int x;
}
void f(){
using namespace A;
using namespace B;
A::x++; // ok
B::x++; // ok
x++; // Reference to 'x' is ambiguous
}
-
全局作用域
引进名称空间后,全局作用域变成了特殊的名称空间。值得说明的是全局就意味着全局,而不是外层的最近名称空间。 -
重载
允许跨名称空间的重载。如:
namespace A {
void f(int);
}
using namespace A;
namespace B {
void f(char);
}
using namespace B;
void f(){
f('a'); // calls B::f(char)
}
-
名称空间允许嵌套。
-
名称空间是开放的,也就是说允许在多个名称空间声明中将各种名称加进一个名称空间中。例如标准库名称空间
std
就是由多个模块组成的。
对类的影响
可以把类看成是一种名称空间。
- 一个类的成员将遮蔽其基类中有同样名称的成员。名称空间对此做出的解释就是,派生类相当于是创建了一个嵌套的名称空间。通过声明可以将基类的函数引入到作用域,例如:
class B{
public:
void f(char);
};
class D: public B{
public:
void f(int);
using B::f; // bring B::f into D to enable overloading
};
void f(D& d){
d.f('c'); // calls D::f(char)!
}
using B::f;
这一方法也可以消除多继承中同名函数的歧义性。同样可用于调整方法的访问方面声明。以替换老式的访问声明方式。
- 清除全局的 static
原来函数可以声明在一个匿名的名称空间以与头文件中的名称发生冲突。
即:
namespace { /**/ }
相当于
namespace unique_name{ /* ... */ }
using namespace unique_name;
与 C 语言的兼容性
- 具有 C 连接的函数也可以放进一个名称空间中:
namespace X{
extern "C" void f(int);
void g(int);
}
不过这要求C 函数的名称的唯一性,即使他们在不同的名称空间中声明,因为他们终穷还是需要按 C 的链接规则来使用,否则将依赖于一些特定的连接名称生成规则来引用 。
网友评论