第十八章 用于大型程序的工具
命名空间
大型程序往往会使用多个独立开发的库,而这些库又会定义大量的全局名字,如类、函数和模板等。当应用程序用到多个供应商提供的库时,不可避免地回繁盛某些名字相互冲突地情况。多个库将名字放置在全局命名空间中将引发命名空间污染( namespace pollution )
命名空间( namespace ) 为防止名字冲突提供了更加可控的机制。
命名空间定义
一个命名空间的定义包括两部分:首先是关键字 namespace
,随后是命名空间的名字。在命名空间名字后面是一系列由花括号括起来的声明和定义。只要能出现在全局作用域中的声明就能置于命名空间内。
namespace cplusplus_primer{
class Sales_data{ /* ... */ };
Sales_data operator+(const Sales_data&, const Sales_data&);
class Query { /* ... */ };
class Query_base { /* ... */ };
} // 命名空间结束后无须分号
每个命名空间都是一个作用域
cplusplus_primer::Query q = cplusplus_primer::Query("hello");
如果其他命名空间(比如 AddisonWesley
)也提供了一个名为 Query
的累,并且我们希望使用这个累代替 cplusplus_primer
中定义的同名类:
AddisonWesley::Query q = AddisonWesley::Query("hello");
命名空间可以是不连续的
命名空间可以定义在几个不同的部分。编写如下的命名空间定义:
namespace nsp{
// ...
}
可能是定义了一个名为 nsp
的新命名空间,也可能是为已经存在的命名空间添加一些新的成员
命名空间的定义可以不连续的特性使得我们可以将几个独立的接口和实现文件组成一个命名空间。此时,命名空间的组织方式类似于我们管理自定义类及函数的方式:
- 命名空间的一部分成员的作用是定义类,以及声明作为类接口的函数及对象,则这些成员应该置于头文件中,这些头文件将被包含在使用了这些成员的文件中
- 命名空间成员的定义部分则只与另外的源文件中
使用命名空间成员
像 namespace_name::member_name
这样使用命名空间的成员显然非常繁琐。
命名空间的别名
namespace cplusplus_primer { /* ... */ }
namespace primer = cplusplus_primer;
// 命名空间的别名也可以指向一个嵌套的命名空间
namespace Qlib = cpluscplus_primer::QueryLib;
Qlib::Query q;
using 声明:扼要概述
一条 using 声明
语句一次只引入命名空间的一个成员。
using
声明引入的名字遵守与过去一样的作用域规则:它的有效范围从 using
声明的地方开始,一直到 using
声明所在的作用域结束为止。
// 命名空间 A 和函数 f 定义在全局作用域中
namespace A{
int i, j;
}
void f(){
using namespace A;
cout << i * j << endl; // 使用命名空间 A 中的 i 和 j
// ...
}
using 指示示例
namespace blip{
int i = 16, j = 15, k = 23;
}
int j = 0;
void manip(){
// using 指示, blip 中的名字被“添加”到全局作用域中
using namespace blip; // 如果使用了 j,则将在 ::j 和 blip::j 之间产生冲突
++i;
++j; // 二义性错误
++::j; // 正确:将全局的 j 设定为1
++blip::j; // 正确:将 blip::j 盛鼎为16
int k = 97; // 当前局部的 k 隐藏了 blip::k
++k; // 将当前局部的 k 设定为 98
}
友元声明与实参相关的查找
当类声明了一个友元时,该友元声明并没有使得友元本身可见。然而,一个另外的为生命的类或函数如果第一次出现在友元声明中,则我们认为它是最近的外层命名空间的成员。
namespace A{
class C{
// 两个友元,在友元声明之外没有其他的声明
// 这些函数隐式地称为命名空间 A 的成员
friend void f2(); // 除非另有声明,否则不会被找到
friend void f(const C&); // 根据实参相关的查找规则可以被找到
};
}
// 此时,f 和 f2 都是命名空间 A 的成员。即使 f 不存在其他声明,
// 也能通过实参相关的查找规则调用 f
int main(){
A::C cobj;
f(cobj); // 正确:通过在 A::C 中的友元声明找到 A::f
f2(); // 错误: A::f2 没有被声明
}
虚继承
C++ 通过 虚继承 virual inheritance
的机制解决菱形继承的问题:虚继承的目的是令某个类做出声明,承诺愿意共享它的基类。其中,共享的基类子对象称为虚基类。在这种机制下,布鲁虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个共享的虚基类子对象