条款07:为多态基类申明virtual析构函数

1、c++明白指出,当derived class对象经由一个base class指针被删除,而该base class带有一个non-virtual 析构函数,其结果未有定义----实际执行时通常发生的是对象derived成分没有被销毁,于是造成一个诡异的局部销毁对象。(子类的析构函数不会被调用)::消除这个问题的做法很简单,给base class一个virtual析构函数,此后删除derived class对象就如你所想要的那般全部销毁。

2、对于不企图当作base class来使用的类,将其析构函数指定未virtual往往是一个馊主意。

classs Point
{
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
}

如上面例子,当析构函数为non-virtual函数时,对象所占空间为64bit(2*int),而当析构函数为virtual函数时,由于对象中会新增virtual table指针,故Point所占空间为96bit(32位系统)

3、pure virtual函数的使用,存在纯虚函数的类位抽象类,无法对其进行实例化(多态实现的一种方式)

请记住:

  • ploymorphic带多态性质的base class应该声明一个virtual析构函数,如果class带有任何virtual函数,他就应该拥有一个virtual析构函数。
  • Class的设计母的不是作为base class使用,或不是为了具备多态性(ploymorphically)就不应该声明virtual析构函数

条款08:别让异常逃离析构函数

1、c++并不禁止析构函数吐出异常,但它不鼓励你这样做,这样是有理由的。

Class Widget
{
public:
....
~Widget() { ... } //Suppose an exception to throw out possibly
} void doSomething()
{
std::vector<Widget> v;
...
} //v在这里将被自动销毁

如上代码,当vector v被销毁,它将负责销毁其内含的所有Widget,假设v内含有10个Widget,而在析构第一个元素期间,有异常被抛出,但其他9个widget还是应该被销毁(否则它们报的任何资源都会发生泄漏),因此应该调用v的各个析构函数。但是若在那些调用期间,第二个widget析构函数又抛出异常,现在两个同时作用的异常,这对C++而言太多了,当两个异常同时存在的情况下,程序不是结束执行就是导致不明确行为。本例中会导致不明确行为。

2、如果你的析构函数必须执行一个动作,而该动作可能会在失败时抛出异常,怎么处理?

//假设你使用一个class负责数据库连接
class DBConnection
{
public:
....
static DBConnection create(); void close();
}

为了确保客户不忘记在DBConnection对象上调用close,一个合理的想法是创建一个用来管理DBConnection资源的class,并在其析构函数中调用close。

class DBConn   //这个class用来管理DBConnection对象
{
public:
...
~DBConn() //确保数据库连接总是会被关闭
{
db.close();
}
private:
DBConnection db;
} //这便允许客户写出这样的代码
{
DBConn dbc(DBConnection::create());//建立一个dbc,通过dbc使用db
....
} //在dbc作用域结束时,DBConnection会随这DBConn的析构而关闭

但时上面代码中,如果调用close顺利,不出现任何异常,那将一切美好。但是若调用出现异常,DBConn析构函数会传播异常,也就是允许程序离开这个析构函数,那就会造成问题,因为抛出了难以驾驭的麻烦。

两个办法可以解决这个问题,DBConn的析构函数可以:

  • 如果close抛出异常就结束程序,通常通过调用abort完成

    DBConn::~DBconn()
    {
    try { db.close(); }
    catch( ... ){
    //制作运转记录,记下对close的调用失败
    std::abort();
    }
    }

    如果程序遭遇一个“于析构期间发生的错误”后无法继续执行,“强迫结束程序”是个合理的选项。毕竟他可以阻止异常从析构函数传播出去(那会导致不明确行为),也就是说调用abort可以抢先置“不明确行为”于死地。

  • 吞下因调用close而发生的异常
    DBConn::~DBconn()
    {
    try { db.close(); }
    catch( ... ){
    //制作运转记录,记下对close的调用失败
    }
    }

    一般而言,将异常吞掉是一个坏主意,因为它压制了“某些动作失败”的重要信息,然而有时候吞下异常也比负担“草率结束程序”或“不明确行为带来的风险”好。

这些办法都没有什么吸引力,问题在于两者都无法对“导致close抛出异常”的情况做出反应;一个较佳的策略是重新设计DBConn接口,使客户有机会对可能出现的问题作出反应

class DBConn
{
public:
...
void close() //供客户使用的函数
{
db.close();
closed = true;
}
~DBConn()
{
if(!closed) //关闭连接,如果客户不那么做的话
{
try{
db.close();
}catch( ... ){
//制作运行记录,记下对close的调用失败
... /吞下异常或记录并结束程序
}
}
}
private:
DBConnection db;
bool closed;
}

此类将在析构函数中可能抛出的异常转移到普通函数中,使得客户获得处理异常得机会。此处即使客户不关心或不处理,而DBConn的析构函数将会完成相应的工作,从而形成双保险

请记住:

  • 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,则析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序
  • 如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

条款09:绝不在构造和析构过程中调用virtual函数

1、base class构造期间virtual函数绝不会下降到derived classes阶层;由于base class构造函数的执行更早于derived class构造函数,当base构造函数执行时derived的成员变量尚未初始化。

Class Transaction{   //所有交易的base class
public:
Transaction();
virtual void logTransaction () const = ; //做出一份因类型不同而不同的日志记录
...
}; Transaction::Transaction()
{
....
logTransaction(); //最后动作标志这笔交易
} class BuyTransaction: public Transaction {
public:
virtual void logTransaction() const; //记录日志
...
}; class SellTransaction: public Transaction {
public:
virtual void logTransaction() const; //记录日志
...
};

如代码中,derived class(BuyTransaction)的virtual logTransaction的将在BuyTransaction被实例化之前调用,从而导致编译器找不到logTransaction的定义。

2、此原则对于析构函数也是类似的。类的析构函数一旦开始调用,程序就会认为该对象已经不存在,不能够使用其内的成员。

3、另外注意被non-virtual函数封装后的virtual函数在构造函数或析构函数中的调用,也是需要禁止的。

请记住:

  • 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(相对于当前执行构造函数和析构函数的那层)

effective c++ (三)的更多相关文章

  1. Effective Java-第三章

    第3章 对于所有对都通用的方法 尽管Object是一个具体类,但是设计它主要是为了扩展,它所有的非final方法(equals,hashCode,toString,clone和finalize)都有明 ...

  2. Java的哪些事

    Java的哪些事--------------------------------------------------Java学习分2个方面: Java语法与Java类库 Java: A simple, ...

  3. 【linux之用户,密码,组管理】

    一.用户及密码 用户账户 超级用户:UID=0 root 普通用户:UID!=0 系统用户: 0<UID<500 为了维持系统的某些功能或者实现某些服务 不能完成登录时候的身份验证 普通用 ...

  4. 《Effective C++》第三版笔记

    阅读此笔记前,请先阅读 <Effective C++>第二版笔记  和  <More Effective C++>笔记 这里只记录与上面笔记不同的条款,主要是 "面对 ...

  5. [.NET] 《Effective C#》快速笔记(三)- 使用 C# 表达设计

    <Effective C#>快速笔记(三)- 使用 C# 表达设计 目录 二十一.限制类型的可见性 二十二.通过定义并实现接口替代继承 二十三.理解接口方法和虚方法的区别 二十四.用委托实 ...

  6. Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  7. Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——7. 消除过期的对象引用

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  10. Effective Java 第三版——10. 重写equals方法时遵守通用约定

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. [转帖]Linux /tmp目录下执行脚本失败提示Permission denied

    Linux /tmp目录下执行脚本失败提示Permission denied https://www.cnblogs.com/linyfeng/p/11087655.html 国产化的环境上 就有一个 ...

  2. 2019.07.05 纪中_B

    今日膜拜:czj大佬orz%%% 2019.07.05[NOIP提高组]模拟 B 组 今天做题的时候大概能判断出题人的考点,可是就是没学过...特别痛苦 T0:栈的定义,模拟就好了T1:感觉像是找规律 ...

  3. @Transactional spring事务回滚相关

    还可以设置回滚点,看下面 /** * 用户登录接口 * * * 1明确事务方法前的命名规则 * 2保证事务方法执行的时间尽可能的短,不允许出现循环操作,不允许出现RPC等网络请求操作 * 3不允许所有 ...

  4. Azure中配置和发布 Nginx docker到互联网

    当Azure build一个Niginx docker 镜像时,无法通过浏览器访问这个镜像,于是想到了把这个网站发布到互联网中,这样就能验证网站是否正确. 本问跳过如何创建Azure Ubantu的虚 ...

  5. O023、理解Nova架构

    参考https://www.cnblogs.com/CloudMan6/p/5410447.html   Compute Service Nova 是OpenStack最核心的服务,负责维护和管理云环 ...

  6. dubbo学习笔记一(服务注册)

    相关的资料 官方文档 官方博客 项目结构 项目说明 [lesson1-config-api] 是一个接口工程,编译后是jar包,被其他工程依赖 [lesson1-config-2-properties ...

  7. json串到java对象

    json串到java对象 前端传入参数json字符串,格式如下: {"语文":"88","数学":"78"," ...

  8. 11、find查找

    1.find查找概述 为什么要有文件查找,因为很多时候我们可能会忘了某个文件所在的位置,此时就需要通过find来查找. find命令可以根据不同的条件来进行查找文件,例如:文件名称.文件大小.文件修改 ...

  9. maven中配置jboss仓库

    有两种方式,一种是在项目的pom.xml中<repositories>中添加,这是配置是针对具体的某一个项目,更多时候,我们想把jboss仓库作为所有项目的仓库,这就需要在maven的se ...

  10. WebSocket是什么原理,为什么可以实现持久连接

    本文摘抄自知乎,原文标题:WebSocket 是什么原理?为什么可以实现持久连接? Websocket只是协议而已. 一.WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化, ...