第十八章 用于大型程序的工具
18.1 异常处理
18.2 命名空间
命名空间定义
- 语法相关:
- 只要能出现在全局作用域的声明就能置于命名空间中
- 命名空间不能定义在函数或类内部
- 每个命名空间是一个作用域。
- 命名空间可以不连续,即同一命名空间可以定义为几个不同的部分,在多处出现
- 在头文件中声明命名空间中的成员,在源文件中定义命名空间中的成员
- 通常不将
#include
放在命名空间中,否则会将该头文件中的所有名字定义为该命名空间中的成员
- 几种命名空间
- 全局命名空间:使用
::
显式指明 - 嵌套命名空间
- 内联命名空间:无需使用该命名空间的前缀,通过外层命名空间就可以直接访问。
- inline必须出现在命名空间第一次定义的地方,后续打开命名空间时可以不加inline
- 程序代码更新版本时经常使用内联空间,当前版本放在内联空间中,历史版本放在非内联空间中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* 文件名:FifthEd.h */ inline namespace FifthEd{ //定义第五版命名空间,是内联,使用时不需显式指定该空间的名字 class Query_base{/* 类的定义 */}; } /* 文件名:FourthEd.h */ namespace FourthEd{ //定义第四版命名空间 class Query_base{/* 类的定义 */}; } /* 文件名:cplusplus_primer.h */ namespace cplusplus_primer{ //将上面两个命名空间嵌套进外层空间 #include"FifthEd.h" //引入头文件中的所有名字 #include"FourthEd.h" } /* 文件名:main.cc */ #include"cplusplus_primer.h" using cplusplus_primer::Query_base; //默认使用第五版中的成员 using cplusplus_primer::FourthEd::Query_base; //手动指定第四版中的成员
- 未命名的命名空间
- 未命名的命名空间可以在一个文件内不连续(是同一个命名空间),但是不可跨越文件(否则是两个无关的命名空间)
- 未命名的命名空间中定义的变量拥有静态的声明周期:在第一次使用前创建,直到程序结束才销毁。
- 如果头文件中定义了未命名的命名空间,则不同源文件中包含了该头文件后,该空间中的名字对应不同实体
- 未命名的命名空间中的名字可以跨越到上一次作用域,因此定义在未命名的命名空间中的名字可以直接使用,不能对未命名的命名空间的成员使用作用域算符
- 应用:未命名的命名空间取代文件中的静态声明
- 原来将全局变量声明为
static
以转变为内部变量(C方式) - 现在将全局变量放在未命名的命名空间中(C++方式,原因见上述语法),但是此时全局变量仍然是外部的,
- 原来将全局变量声明为
- 全局命名空间:使用
使用命名空间成员
- 命名空间的别名:
namespace new_name = old_name1::old_name2;
- 一个命名空间可以有多个别名,但不能在未定义命名空间之前就声明别名
using
声明:using my_namespace::mem;
- 一次只能引入命名空间的一个成员
- 声明的名字的作用域与using语句本身的作用域一致
- 在类作用域中
using
声明只能声明基类成员
using
指示:using nemespace my_namespace;
- 引入命名空间中所有名字
- using指示将命名空间注入到外层作用域,即将命名空间中所有名字出现在最近的外层作用域中(相当于using声明的外层作用域)
- 不可出现在类作用域
- 命名空间污染:
- 使用了多个命名空间的using指示后,外层作用域中来自不同命名空间的名字可能发生冲突,这种冲突允许存在,但是使用时需要使用
::
明确指定版本 - 在头文件中,不要在全局作用域中使用using声明/指示,最多在函数、命名空间中使用
- using指示引发的二义性错误只有在使用冲突名字的地方才会被发现,难以定位bug
- 尽量使用using声明而非using指示
- 使用了多个命名空间的using指示后,外层作用域中来自不同命名空间的名字可能发生冲突,这种冲突允许存在,但是使用时需要使用
类、命名空间与作用域
- 名字查找的例外:给函数传递类类型对象/引用/指针时,先在常规的作用域中查找函数名,随后还会在实参类(及其基类)所属的命名空间中查找函数名。
- 这个规则使得概念上作为接口一部分的非成员函数不需单独using声明就可被程序使用
- 例子:
std::cin>>str;
表达式中,作用域中没有声明operator>>()
函数,但是仍可以使用,这是因为在istream
(实参std::cin
所属类)和string
(实参str
所属类)所在的命名空间中进行了查找。否则需要显式声明:using std::operator>>
,使用operator>>(std::cin, str);
- 友元相关:
- 当类声明友元时,还需要在类外给出友元的正式声明
- 一个未声明的类/函数若第一次出现在友元声明中,则认为它是最近的外层命名空间的成员。
1 2 3 4 5 6 7 8 9 10 11
namespace A{ class C{ //这2个友元声明时还没有正式声明,认为它是最近的外层空间的成员,即隐式声明为空间A的成员 friend void f2(); //没有形参 friend void f(const C &); //接受C类型对象作为实参 }; } int main(){ A::C cobj; f(cobj); //对,f被隐式声明为A的成员,且实参决定会在A中查找函数f f2(); //错,虽然f2被隐式声明为A的成员,但未显式指明 }
重载与命名空间
using
声明using
声明语句声明的是一个名字,而非特定的函数,也就是包括该函数的所有版本,都被引入到当前作用域中。using
声明引入的函数将重载该声明语句所属作用域中已有的同名函数。
using
指示- 若命名空间中函数名与外层作用域中函数同名,即使函数同名同参也不会报错,只需要使用时指明版本
18.3 多重继承与虚继承
多重继承
- 可以从多个基类中继承构造函数,但是这些构造函数必须形参列表不同
- 如果相同,则派生类必须为这种形参列表的构造函数定义自己的版本
类型转换与多个基类
- 在派生类向基类的转换中,如果有多个基类,编译器不会进行比较,转换到任何基类一样好
- 对象的指针/引用的静态类型决定了哪些成员可见
多重继承下的类作用域
- 在派生类中使用了某个名字,则程序并行的在多个基类中查找名字
- 派生类继承多个基类的同名成员合法,只是使用时需要
::
指明版本 - 派生类只是引入潜在的二义性,如果不调用该重名的对象,则不会报错
- 只有使用该重名对象时,才会产生二义性报错
- 该名字在多个基类中是形参列表不同的函数
- 该名字在一个基类中是private,而在另一个基类中是public/protected
- 该名字在一个基类中直接找到,而在另一个基类的间接基类中找到
- 避免这种二义性的方法是在派生类中再定义一次这个名字,覆盖基类名字,避免在基类中查找
- 派生类继承多个基类的同名成员合法,只是使用时需要
- 当一个类拥有多个基类时,有可能出现派生类从两个或更多基类中继承了同名成员的情况。此时,不加前缀限定符直接使用该名字将引发二义性。
虚继承
- 背景:菱形继承中,间接基类应该只有一个,如果不使用虚继承,则间接基类在派生类对象中有两个部分
- 虚继承:令某个类做出声明,承诺愿意共享它的基类。被共享的基类子对象称为虚基类,不论虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个共享的虚基类子对象。
- 经常并不知道一个类是否会被继承多次,因此不知道由它而来的派生是否应该是虚派生
- 实际编程中,位于中间层次的类将其继承基类的方式声明为虚继承并不会出问题。虚派生只影响从虚基类的派生类中进一步派生出的类,它不影响虚基类的派生类。
- 语法相关:
- 在派生列表中添加
virtual
,表示后续的派生类共享虚基类的同一份实例 - 菱形继承:类B定义了成员x,D1和D2由B虚继承得到,D继承自D1和D2,则在D的作用域中,x通过两个基类都可见。若通过D的对象使用x,有几种可能:
- 若D1和D2中都未定义x,则x被解析为B的成员,不存在二义性。因为只在虚基类中有定义
- 若D1或D2其中之一定义了x,则x被解析为D1或D2的成员,不存在二义性。因为D1和D2是派生类,位于内层作用域,优先级更高
- 若D1和D2中都定义了x,则直接访问x时是二义性。因为D1和D2的优先级相同
- 解决二义性最好的方法就是在派生类中为成员自定义新的实例
- 在派生列表中添加
构造函数与虚继承
- 在虚派生中,虚基类由最终的派生类在其构造函数初值列表中初始化(越过了继承链),而非由其直接派生类初始化,否则被重复初始化
- 只要创建了虚基类的派生类对象,该派生类的构造函数就会越过继承链初始化虚基类
- 含有虚基类的对象的构造顺序:
- 首先使用提供给最终派生类构造函数的初值来初始化虚基类(否则虚基类默认初始化)
- 一个类可有多个虚基类,这些虚基类的初始化顺序是它们在派生列表中的顺序
- 然后按照直接基类在派生列表中的顺序初始化非虚基类
- 构造派生类时,编译器按照直接基类的声明顺序对其依次检查,若基类中含有虚基类,则先构造虚基类,然后按照声明顺序逐一构造其他非虚基类