Effective C++笔记(5)—条款7/8/9

为多态基类声明virtual析构函数

主要是两个点:
1.多态的基类需要声明一个virtual析构函数。
2.如果类不是用作基类或者具备多态性,则不该声明virtual析构函数

用基类指针表示子类对象可视作多态,如果基类不具备虚析构,那么在delete调用的时候,将不会正确调用子类析构函数。C++构造的顺序是先父类再子类,析构顺序反之,可以看看当不具备析构函数时的效果如何。

#include <iostream>
using namespace std;
class base
{
public:
    base(){ cout << "base class constructor" << endl; }
    ~base(){ cout << "base class destructor" << endl; }
};
class derived :public base
{
public:
    derived(){ cout << "derived class constructor" << endl; }
    ~derived(){ cout << "derived class destructor" << endl; }
};
int main()
{
    base *ptr = new derived(); 
    delete ptr;
    system("pause");
    return 0;
}
//base class constructor
//derived class constructor
//base class destructor

以上是条款7所描述概要。


别让异常逃离析构函数

析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?

void a()
{
    b();
}
void b()
{
    c();
}
void c()
{
    throw 1;
}

假设在C函数中,抛出异常,而a函数调用了b,b函数调用了c。那么当c调用过程中抛出异常,那么这个异常会抛到到b再从b抛到a,在a中的try-catch中捕获异常。

构造函数中是可以抛出异常的。C++标准指明析构函数不能、也不应该抛出异常。

C++异常处理模型是为C++语言量身设计的,更进一步的说,它实际上也是为C++语言中面向对象而服务的。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。

上面的论述C++异常处理模型它其实是有一个前提假设——析构函数中是不应该再有异常抛出的。试想,如果对象出了异常,现在异常处理模块为了维护系统对象数据的一致性,避免资源泄漏,有责任释放这个对象的资源,调用对象的析构函数,可现在假如析构过程又再出现异常,那么请问由谁来保证这个对象的资源释放呢?而且这新出现的异常又由谁来处理呢?不要忘记前面的一个异常目前都还没有处理结束,因此这就陷入了一个矛盾之中,或者说无限的递归嵌套之中。所以C++标准就做出了这种假设,当然这种假设也是完全合理的,在对象的构造过程中,或许由于系统资源有限而致使对象需要的资源无法得到满足,从而导致异常的出现,但析构函数完全是可以做得到避免异常的发生,毕竟你是在释放资源呀!

如果析构函数有可能抛出异常,那么析构函数中应该捕捉这些异常。

~ClassName()
{
  try{
      do_something();
  }
  catch(){  //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外。
   }
}

参考:
Effective C++
http://blog.csdn.net/memewry/article/details/7940168


绝不在构造和析构过程中调用Virtual函数

编译是没有问题的,但不建议这么做,理由如下:

1.不在构造函数中调用虚函数

创建一个子类对象,先会调用父类构造函数,再调用子类构造函数。
vtable是每个class一个,而不是每个object一个。vptr是每个对象一个。
构造子类对象时,先调用父类构造函数,此时子类的vtable还没有形成,vptr指向父类的vtable,调用的函数还是父类的版本。

也即,父类对象构造完成之前,子类对象还未构造,virtual函数就不是virtual函数,只会调用父类内部的版本。

2.不在析构函数中调用虚函数

当子类对象进行析构时,它将先调用自己的析构函数,再调用父类的析构函数。假设一个派生类的对象进行析构,首先调用了派生类的析构,然后在调用基类的析构时,遇到了一个虚函数,这个时候有两种选择:Plan A是编译器调用这个虚函数的基类版本,那么虚函数则失去了运行时调用正确版本的意义;Plan B是编译器调用这个虚函数的派生类版本,但是此时对象的派生类部分已经完成析构,“数据成员就被视为未定义的值”,这个函数调用会导致未知行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值