第15章 友元、异常和其他
- 友元类
- 友元类方法
- 嵌套类
- 引发异常、try块和catch块
- 异常类
- 运行阶段类型识别(RTTI)
- dynamic_cast和typeid
- static_cast、const_cast和reiterpret_cast
15.1 友元
可以将类作为友元,友元类的所有方法都可以访问原始类的私有成员和保护成员。也可以只将特定的成员函数指定为另一个类的友元。
15.1.1 友元类
例如电视机Tv和遥控器Remote,既不适用is-a也不适用has-a,但遥控器可以改变电视的状态,应将Remote类作为Tv类的一个友元。
友元声明可以位于公有、私有、保护部分,所在的位置无关紧要。
class Tv
{
public:
friend class Remote; // Remote can access Tv private parts
};
class Remote
{
public:
bool volup(Tv& t) {return v.volup();}
}
15.1.2 友元成员函数
class Tv; // forward declaration前向声明,避免Remote不知道Tv类。
class Remote {...};
class Tv
{
friend void Remote::set_chan(Tv& t, int c);
};
15.1.3 其他友元关系
可以互为友元类,互相影响。
15.1.4 共同的友元
函数需要访问两个类的私有数据时,可以同时是这两个类的友元。
15.2 嵌套类
可以将类声明放在另一个类中,在另一个类中的声明被称为嵌套类(nested class)。
class Queue
{
// class scope definitions
// Node is a nested class definition local to this class
class Node
{
public:
Item item;
Node* next;
Node(const Item& i) : item(i), next(0) {}
};
};
bool Queue::enqueue(const Item& item)
{
if (isFull()) return false;
Node* add = new Node(item); // create, initialize node
...
}
15.2.1 嵌套类和访问权限
- 作用域:
- 嵌套类在另一个类的私有部分声明:只有这个类知道他。
- 在保护部分声明:对类可见,外部不可见。
- 在公有部分声明:都可见。
- 访问控制:对嵌套类访问权的控制规则与常规类相同。
15.2.2 模板中的嵌套
模板类包含嵌套类不会带来问题。
15.3 异常
15.3.1 调用abort()
cstdlib或stdlib.h中的abort(),终止程序。
double hmean(double a, double b)
{
if (a == -b)
{
std::abort();
}
return 2.0 * a * b / (a + b);
}
15.3.2 返回错误码
或使用函数的返回值来指出问题,istream能告诉是否成功:
bool hmean(double a, double b, double* ans)
{
if (a == -b)
{
*ans = DBL_MAX;
return false;
}
else
{
*ans = 2.0 * a * b / (a + b);
return true;
}
}
if (hmean(x, y, &z)) ...;
15.3.3 异常机制
异常机制的组成部分:
- 引发异常
- 使用处理程序捕获异常
- 使用try块
double hmean(double a, double b)
{
if (a == -b)
throw "123456";
return 2.0 * a * b / (a + b);
}
try
{
z = hmean(x, y)
}
catch (const char* s)
{
std::cout << s << std::endl;
}
15.3.4 将对象用作异常类型
通常,引发异常的函数将传递一个对象。可以使用不同的异常类型,来区分不同的函数在不同情况下引发的异常。另外对象还可以携带信息。
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a=0, double b=0) : v1(a), v2(b) {}
void mesg();
};
inline void bad_hmean::mesg()
{
std::cout << "123456";
}
class bad_gmean {}; // ...
double hmean(double a, double b)
{
if (a == -b) throw bad_hmean(a, b);
return 66.6;
}
double gmean(double a, double b)
{
if (a < 0 || b < 0) throw bad_gmean(a, b);
return 77.7;
}
int main()
{
while (cin >> x >> y)
{
try
{
z = hmean(x, y);
a = gmean(x, y);
}
catch (bad_hmean& bg)
{
bg.mesg();
continue;
}
catch (bad_gmean& hg)
{
hg.mesg();
continue;
}
}
}
15.3.5 异常规范和C++11
- C++98有,但是C++11废弃了的功能:异常规范:double marn(double) throw(bad_thing);提示可能要用try
- C++11新增但不建议用的noexcept关键字指出函数不会引发异常:double marn(double) noexcept;
15.3.6 栈解退
栈解退unwinding the stack:函数由于出现异常而终止,程序将释放栈中的内存,直到找到一个try块中的返回地址。随后控制权转到块尾的异常处理程序。
P506
15.3.7 其他异常特性
15.3.8 exception类
exception头文件定义了exception类,代码可以引发exception异常,也可以将其用作基类。
#include <exception>
class bad_hmean : public std::exception
{
public:
const char* waht() {return "666";}
}
try{
...
}
catch(std::exception& e){
cout << e.what() << endl;
}
异常类型:
- stdexcept异常类
- logic_error:逻辑错误
- domain_error:定义域
- invalid_argument:函数传递了意料外的值
- length_error:没有足够空间
- out_of_bounds:索引错误
- runtime_error:运行期间发生但难以预计和防范的错误
- range_error:计算结果不在函数允许的范围内,但没有上溢或下溢
- overflow_error:整型和浮点型上溢错误(计算结果超过了某种类型能表示的最大数量级)
- underflow_error:浮点数下溢错误(计算结果小于浮点类型能表示的最小非零值)
- logic_error:逻辑错误
- bad_alloc异常和new:对于使用new导致的内存分配问题,让new引发bad_alloc异常。头文件new
- 空指针和new:有一种在失败时返回空指针的new:int* pa = new (std::nothrow) int[500];
#include <iostream>
#include <new>
#include <cstdlib> // for exit(), EXIT_FAILURE
struct Big
{
double stuff[20000];
}
int main()
{
Big* pb;
try
{
cout << "Trying to get a big block of memory:\n";
pb = new Big[10000]; // 1,600,000,000 bytes
}
catch (bad_alloc& ba)
{
cout << ba.what() << endl;
exit(EXIT_FAILURE);
}
}
15.3.9 异常、类和继承
互相关联:
- 可以像标准C++库那样,从一个异常类派生出另一个
- 可以在类定义中嵌套异常类声明来组合异常
- 这种嵌套声明本身可被继承,还可用作基类
15.3.10 异常何时会迷失方向
有两种情况会导致引发异常后出现问题:
- 意外异常:在带异常规范的函数中引发,但没有与规范列表中的某种异常匹配
- 未捕获异常:异常不是在函数中引发(或函数没有异常规范),没有被捕获时(没有try块或没有匹配的catch块)。
15.3.11 有关异常的注意事项
应在设计程序时就加入异常处理功能,而不是以后再添加。
15.4 RTTI
RTTI运行阶段类型识别(Runtime Type Identification)。
15.4.1 RTTI的用途
例如基类指针,判断到底是指向哪一个类的对象。
15.4.2 RTTI的工作原理
3个支持RTTI的元素:
- dynamic_cast:使用一个指向基类的指针来生成一个指向派生类的指针,否则返回空指针。
- typeid:返回一个指出对象的类型的值。
- type_info:存储了有关特定类型的信息。
RTTI只适用于包含虚函数的类。
dynamic_cast能够回答“是否可以安全的将对象的地址赋给特定类型的指针”:
Superb* pm = dynamic_cast<Superb*>(pg);
typeid能够确定两个对象是否为同种类型,typeid返回一个对type_info对象的引用(头文件typeinfo)
typeid(Magnificent) == typeid(*pg);
typeid(*pg).name(); // 通常是类的名称
考虑使用虚函数和dynamic_cast来代替typeid,更简洁可读。
15.5 类型转换运算符
C语言的类型转换不够规范,看不出目的。C++增加了更严格的类型转换:
- dynamic_cast:使得能够在类层次中进行向上转换
- const_cast:使指向const值的指针的值可以修改
- static_cast:这个进行向下转换是合法的
- reinterpret_cast
15.6 总结
- 友元使得能够为类开发更灵活的接口。类可以将其他函数、其他类、其他类的成员函数作为友元。
- 嵌套类是在其他类中声明的类。
- C++异常机制。try…catch…
- RTTI运行阶段类型信息。dynamic_cast, typeid, type_info
- dynamic_cast, static_cast, const_cast, reinterpret_cast
15.7 复习题
15.8 编程练习
略
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 cdd@ahucd.cn