C++的35个技巧阅读笔记(二)

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,他们必须删除,否则便会资源泄漏。

  1. catch - by - pointer和语言本身建立起来的惯例有矛盾。因此不推荐使用catch(exception *ex)
  2. catch - by - value可以消除上述“exception是否需要删除”等问题,但是每当exception被抛出,就得复制两次,此外也会引起切割(slicing)问题。catch(exception)
  3. 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());
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值