文章目录
11.禁止异常(exception)流出析构函数之外
两种情况下destructor会被调用
- 1、当对象在正常情况下被销毁,也就是它离开了它的生存空间或是被明确地删除;
- 2、当对象被exception处理机制(也就是exception传播过程中的(栈展开)stack-unwinding机制)销毁。
Session::~Session()
{
try{
logDestruction(this);//记录对象释放
}
catch(...){ } //通过三个点...捕捉所有异常,避免程序崩溃。
}
让异常传递到析构函数之外有两个原因
- 1.可以避免terminate函数在exception传播过程的栈展开(stack-unwinding)机制中被调用。
- 2.可以协助确保析构函数完成其应该完成的所有事情。
12.了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异
- 1.异常对象被抛出作为exception时,总是会发生复制(copy),无论被捕捉的exception是以by value或by reference方式传递,而交到catch子句手上的正是个副本。对象作为函数参数传递时候,不一定需要被复制(by reference)
- 2 .当对象被复制当作一个exception,复制行为是由对象的copy constructor执行的,而这个constructor相应于改对象的“静态类型”而非“动态类型”。和其他所有“C++复制对象”的情况一样,复制动作永远是以对象的静态类型为本。(条款25除外)。
1.catch(Widget& w) //效率更好,推荐使用??
{
... //处理exception
trow; //重新抛出此exception
}
catch(Widget& w) //捕捉Widget exceptions
{
...
trow w; //注意:传播被捕捉的exception的一个副本w(拷贝),还可能改变异常类型(一定是Widge)
}
2.catch (Widget w) //得付出“被抛出物”的“两个副本”的构造代价,其中一个构造动作用于“任何exceptions都会产生临时对象”,
//另一个用于将临时对象复制到w,此后析构两个对象也有代价,不推荐
catch (Widget& w) //付出“被抛出物”的“单一副本”的构造代价
当调用函数,程序的控制权最终会返回到函数调用处,但是抛出异常,控制权永远不会回到异常的地方.
13.以引用(reference)方式捕捉异常(exceptions)
通过指针catch by pointer,传值catch - by - value和引用catch - by - reference这三种方法,传异常进入catch函数:
首先考虑catch by pointer,只有通过指针抛出异常才能不拷贝对象.
- 1.throw by pointer是唯一在搬移“异常相关信息”时不需复制对象的一种做法:
class exception { ... };
void someFunction()
{
static exception ex; //static 约束很重要,若为局部变量catch收到的指针,指向不存在的对象
...
throw &ex; //抛出一个指针,指向ex
}
void doSomething()
{
try {
someFunction(); //可能抛出一个exception* 。
}
catch(exception *ex) { //捕捉到exception*,
... //没有任何对象被复制
}
}
- 2.或者抛出heap对象:
void someFunction()
{
throw new exception; //或者抛出一个heap-based object,并假设operator new 本身不会抛出exception
}
但是他们应该删除他们获取的指针吗?
如果exception object被分配于heap,他们必须删除,否则便会资源泄漏。
- catch - by - pointer和语言本身建立起来的惯例有矛盾。因此
不推荐使用
。catch(exception *ex)
- catch - by - value可以消除上述“exception是否需要删除”等问题,但是每当exception被抛出,就得
复制两次
,此外也会引起切割(slicing)问题。catch(exception)
- catch - by - reference没有对象删除问题,没有切割问题,而且exception object只会被复制一次。
强烈推荐使用
。catch(exception *ex)改为catch(exception& ex)
14. 谨慎使用异常规格exception specifications
C++ 提供语法用于声明函数所抛出的异常;
异常声明作为函数声明的修饰符,写在参数列表后面:
1. /* 可能抛出任何异常 */
void func1();
2. /* 只能抛出的异常类型:char 和 int */
void func2() throw(char, int);
3. /* 不抛出任何异常 */
void func3() throw();
例子:
- 1.千万不要为template提供意味深长的exception specifications。
- 2.如果A函数内调用了B函数,而B函数无exception specifications,那么A函数本身也不要设定exception specifications。特别是回调函数(callback)的时候。
- 3.处理“系统”可能抛出的exception。最常见的是bad_alloc,那是在内存分配失败时,由operator new 和operator new[]抛出的。可以使用自己定义的Unexpected-Exception取代非预期的exception。
class UnexpectedException {};
void convertUnexpected()
{
throw UnexpectedException();
}
并以convertUnexpected取代默认的unexpected函数
set_unexpected(convertUnexpected);
一旦完成这些部署,任何非预期的exception便会导致convertUnexpected被调用,于是非预期的exception被一个新的、类型为UnexpectedException的exception取而代之。
2018.8.18补充:
15.了解异常处理(exception handing)的开销
使用try语句块,代码整体膨胀5%-10%,执行速度亦下降这个数。和正常函数返回动作比较,由于抛出异常而导致的函数返回,其速度可能比正常速度慢3个数量级。因为exception应该是罕见的,80-20法则(条款16)告诉我们,如此的事件应该不会对一个程序的整体性能有太大的冲击。注意:为了让exception的相关成本最小化,只要能够不支持exception,编译器便不支持;请将你对try语句块和exception specifications 的使用限制于非用不可的地点,并且在真正异常的情况下才抛出异常。
16.谨记 80 — 20法则
软件的整体性能几乎总是由其构成要素(代码)的一小部分决定。1、大部分人采用的瓶颈查找法是“猜”,用经验猜、用直觉猜。大部分程序员对程序性能特质,都有错误的直觉(程序的性能特质倾向于高度的非直觉性)。可行之道是完全根据观察或实验来识别出造成你心痛的那20%代码。而辨别之道是借助某个程序分析器。
lazy evaluation可在一下四个场合排上用场1、引用计数,避免非必要的对象复制。在真正需要之前,不要着急为某物做一个副本。2、区分读和写,读的成本比较低,写的成本比较高,因此使用COP - On - Write(cow技术)写时拷贝。3、Lazy Fetching (缓式取出)避免非必要数据库读写。产生一个对象的外壳,不从磁盘读取任何字段数据,当对象内的某个字段被需要了,程序才从数据库中取回对应的数据。4、Lazy Expression Evaluation(表达式缓评估)避免非必要数值计算动作。
17.考虑使用 lazy evaluation(缓式评估)
lazy evaluation可在一下四个场合排上用场:
- 1、引用计数,避免非必要的对象复制。在真正需要之前,不要着急为某物做一个副本。
- 2、区分读和写,读的成本比较低,写的成本比较高,因此使用COP - On - Write(cow技术)写时拷贝。
- 3、Lazy Fetching (缓式取出)避免非必要数据库读写。产生一个对象的外壳,不从磁盘读取任何字段数据,当对象内的某个字段被需要了,程序才从数据库中取回对应的数据。
- 4、Lazy Expression Evaluation(表达式缓评估)避免非必要数值计算动作。
18.分期摊还预期的计算成本
超急评估(over - eager evaluation):在被要求之前就先把事情做下去。思想是:预期要用到某个数值,提前保留下来。
- 1、策略是:使用一个局部缓存,将相对昂贵的“数据库查询动作”以相对价廉的“内存内数据结构查找动作”取而代之。
- 2、预先取出:读整块数据块,比分成两三次每次读一小块数据,速度上块很多。如果某处数据被需要,通常其临近的数据也会被需要。
19.了解临时对象的来源
区别局部对象,临时对象是不可见的,只要产生一个non-heap object而没有命名,便诞生一个临时对象。通常发生于两种情况1、当隐式类型转换被施行起来以求函数调用能够成功。2、当函数返回对象时候。可以通过“返回值优化”见条款20。
20.协助完成“返回值优化(RVO)”
如果返回值必须返回对象(如:operator*),那就返回对象。但是可以通过特殊写法,让编译器消除临时对象的成本。
//最优效率的做法
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.number() * rhs.number(), lhs.denominator() * rhs.denominator());
}