命名空间及匿名命名空间

把命名空间单独提出来描述是因为:命名空间的功能几乎和前缀是一致的。

1.       使用命名空间的必要性

          在一个大的项目中,当多时合作时,多个人编写的库头文件中,不可以避免的存在变量名相同的冲突,特别都是作为全局变量存在的时候,这种冲突就更为明显,如何解决这种冲突?方法就是使用命名空间。

          比如说,每个人所定义的变量,都以他自己的名字的缩写为前缀,假如名字缩写为sxl,则定义一个全局变量CMD,则应该该为sxlCMD,但是这种组合方式从旁观者的角度看来是不顺眼的。为了解决这种问题,则使用命名空间。把sxl定义为命名,所以属于它的范围内的变量及函数都属于这个名字的空间。

命名空间的定义方法:

命名空间的使用方法为:

在编写头文件时,一个良好的风格,就是尽量使用命名空间。

有名字的命名空间是external linkage的,而匿名的命名空间是internal linkage的。

2.       命名空间的使用规则

1.  命名空间的使用(using namespace xxx)可以在函数内使用,而命名空间只限制在该函数内。

2.  C++手册中规定,除了命名空间std外,其它的命名空间都可被修改(扩展)。如果修改命名空间std,则将导致不可预料的行为(undefined behavior)。但是在VS2008中验证,编译器并不阻止用户扩展std命名空间—可以通过编译。只能说明扩展命名空间导致的不可预料的后果由程序员承担。

3.  不要在头文件中使用匿名命名空间,提倡在cpp/cc文件中使用匿名命名空间来代替static关键字。(!!尤其是绝对不要在头文件的匿名命名空间中定义类,原因见参见匿名命名空间的特殊点知识)

4.  不能在命名空间中重载new/delete操作符,因为这很容易导致调用二义性。

5.  对于长串的层级命名空间,如果每次都顺序列出命令空间,则使用不方便,可以使用命名空间的别名。如:

namespace curSpace=std::tr1::regex_constants;

using namespace curSpace;

6.  使用命名空间别名时,只能一次性赋值,不能重赋值。这个类似于变量的引用:只能定义时给引用赋值,不能给引用改变引向。

namespace curSpace=std::tr1::regex_constants;

curSpace= std::tr1;                // error C2882:illegal use of namespaceidentifier in expression

7.            命名空间的其它用法

命名空间除常见的功能用于识别同名变量外/函数/类外,还有一个用法:版本控制:程序的有些功能,在不同的版本中,可能功能是不一样的,如果使用if/else来控制,有时可读性不太好,则这时可以使用命名空间。

 

8.  命名空间的开销

编译器通常函数的原型,函数的类名,函数的空间名来生成/定位一个独一无二的函数名字,这些查找都是静态的(在编译期执行)。因此,层次性的多级命名空间/命名空间切换只会带来编译时一些开销,而不会带来运行时的时间与内存开销。

因此,使用命名开销是值得提倡的。(这种微小的编译开销是值得的)

9.  由命名空间引起的include用法的变革。

由于命名空间的原因,在新C++中,提倡引用头文件不再带.h。引用C头文件也不带.h,但是带前缀c。例如:

#include <cstdio>                 //引用C头文件c为前缀

#include<cassert>               //引用C头文件c为前缀

#include <array>                               //引用C++头文件

这是因为,C++会自动把许多声明归到std命名空间中去,而把C头文件中的声明加入到全局命名空间中去。(全局命名空间是不提倡的)

C++仍然支持include的各种老的用法,但是不再提倡。

 

 

3.       匿名命名空间的特殊点


匿名命名空间(unnamed namespace, anonymousnamespace)

匿名命名空间的一些特殊点会影响到它的使用,所以有必要单独列出来。

通常除了类的静态成员函数和静态数据成员外,其它的static需求都提倡使用匿名命名空间来代替。

Ø  代替static用法

在标准C中,如果期望一个变量或者一种函数只能被本源文件(同个translation unit)使用,通常的做法是使用static来修饰这个变量/函数,来保证这个变量/函数具有内部链接(internal linkage)。这种技术称为信息隐藏。

在C++中,仍然支持这种做法,但是已经不提倡,C++只提倡static仅仅用于修饰类成员,不再提倡用static来修饰非类成员。在以后的编译器中,可能会对使用static来修饰非类成员的方式给出编译警告。

 

staticVarstaticFunc只能在本源文件中使用,不同的源文件中可以使用同名变量及函数,它们相互隐藏,不会产生命名冲突。

注意:在VS2008上验证,只有顶级匿名命名空间才具有隐藏效果(不知道是C++语言是这么规定的,还是VS编译器的bug)。如果一个子匿名命令空间是定义在一个命名的命名空间中,则该匿名命令空间中定义的类和实例都可被其它源文件访问。

Ø  不要在.h文件中使用匿名命名空间

提倡在cpp/c源文件中使用unnamed namespace,不要(不提倡)在.h文件中什么匿名命令空间,因为其将生成大量不被使用类型或者数据---这些数据不是副本,是全局唯一的(原因见下面)。

这里重提一下:.h文件与.cpp文件。.h文件不是编译单元,所以不能直接编译.h文件;.cpp/.cc文件才是编译单元,编译器可以直接编译.cpp/.cc文件。那么如果一个.cpp文件include了.h文件,是什么效果呢?直白的意思就是:把所有(直接/间接)include的.h的文件内容直接复制到该.cpp文件中,再对该.cpp文件编译。这就意味着,如果在一个头文件中定义了unnamed namespace,那么所有include该.h的编译单元(.cpp/.cc文件),都会完全包含该.h文件中的所有内容,那么,一个.cpp/.cc中包含的匿名空间里的数据,它自己当然能访问了。无论它具有的是内部链接属性,还是外部链接属性,都不影响访问。(除非对访问对象有链接属性要求的场景:比如模块的非类型参数-数据实参)。

原因分析:虽然unnamed namespce是internal linkage,但是如果其在.h文件中使用,并且该.h文件被多个.cpp文件引用的时候,编译器会为这个匿名的命名空间在不同的.cpp中生成不同的唯一的空间名字(即开发者使用匿名命名空间,但是编译器编译时仍然给它生成了一个唯一的名字)。

比如:对于匿名命名空间中的类型struct LocalType,其在编译单元src1.cpp的内部名字是::a0b2de::LocalType,在编译单元src2.cpp的内部名字是::d3fe23d:: LocalType。

对于开发者来说,看到的只是LocalType,但是对于编译器来说,其看到的是两个不同的类型(因为其在不同的命名空间中)。所以意味着当该.h文件被include时,其在每个cpp文件中有一份命名空间不同的同名类型LocalType。如果在该匿名命令空间中定义了类型,那么意味着潜在地存在错误。考虑下面这种情况。

在匿名命名空间中定义了一个新的用户类型,如果这个匿名命名空间在头文件中,那么其将被inlcude到各个源文件并且一变多成为多个不同的类型。

在不同的(cpp/cc)源文件中,用户看到的同一个类名,但是在不同的translation unit中,编译器为它们添加了不同的C++修饰名(命名空间),即在不同的编译单元中,它们是不同的类型。如果这种情况下,需要使用type_info类的信息进行比较,就会得出错误的结果。代码:

a) 在头文件中定义匿名命名空间

 

b) 分别在两个cpp文件中使用Type2Type类型

在第一个源文件中

 

 

在第二个源文件中

 

其实在这两个源文件中使用的LocalType类并不是同一个类—编译器在各自的编译单元里为它们添加了不同的C++修饰符。

C ) 测试代码

 

 

D) 测试结果

在调试器中观察其值:

 

可以看到除了这两个类的基本名字相同外,其原始名字(即修饰名)和type_info的比较结果都是不同的(type_info使用原始名字进行比较)。

原始名字中的不同(黄色)部分,就是编译器为不同编译单元中的LocalType生成的匿名空间的空间名(参考"C++修饰名"知识点),由此证明在上面两个源文件中(init_1.cpp/init_2.cpp)使用的LocalType并不是同一个类。

(其中编译器生成的匿名空间名字猜测是文件路径的某种编码信息,比如把第二个源文件的文件名由init_2.cpp改成init_2_ex.cpp,则其相应的黄色部分变成?A0xafb6ef69; 而改动init_2.cpp的文件内容,重新编译,则黄色部分的值并不改变。即匿名空间的修饰名包含源文件路径信息)

 

Ø  在模板元设计中小心使用匿名命名空间

匿名空间具有内部链接属性,其内部定义的函数/类型/变量/常量在不同的编译单元都是不同的,所以在进行模板设计时,尤其要小心,以防止传入的类型参数来自于不同编译单元的匿名命名空间,这样开发者字面意思看到的是同一类型,但是模板元逻辑处理中,竟然是不同的类型,很容易被认为是编译器的bug。

出于这个目的,我们可以考虑在模板元设计中禁止使用匿名命名空间。

这其实是上一条规则中的一个实例:因为模板元代码都是在.h文件中的,而"不要在.h文件中使用匿名命名空间"

 

Ø  无法引用定义在匿名命名空间中其它编译单元里的类型和数据。

正是因为上述的原因(编译器为不同编译单元里的匿名命名空间生成了不同的名字),所以我们无法引用定义在其它编译单元的匿名命名空间中定义的类型和数据,哪怕这些类型和数据来自于同一个.h文件――在这种情况下,我们引用的类型和名字,并不是我们逻辑上期望的那个类型和名字,这即意味着一个实现错误

 

 

 

展开阅读全文

没有更多推荐了,返回首页