effective c++ (三)
条款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++ (三)的更多相关文章
- Effective Java-第三章
第3章 对于所有对都通用的方法 尽管Object是一个具体类,但是设计它主要是为了扩展,它所有的非final方法(equals,hashCode,toString,clone和finalize)都有明 ...
- Java的哪些事
Java的哪些事--------------------------------------------------Java学习分2个方面: Java语法与Java类库 Java: A simple, ...
- 【linux之用户,密码,组管理】
一.用户及密码 用户账户 超级用户:UID=0 root 普通用户:UID!=0 系统用户: 0<UID<500 为了维持系统的某些功能或者实现某些服务 不能完成登录时候的身份验证 普通用 ...
- 《Effective C++》第三版笔记
阅读此笔记前,请先阅读 <Effective C++>第二版笔记 和 <More Effective C++>笔记 这里只记录与上面笔记不同的条款,主要是 "面对 ...
- [.NET] 《Effective C#》快速笔记(三)- 使用 C# 表达设计
<Effective C#>快速笔记(三)- 使用 C# 表达设计 目录 二十一.限制类型的可见性 二十二.通过定义并实现接口替代继承 二十三.理解接口方法和虚方法的区别 二十四.用委托实 ...
- Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——7. 消除过期的对象引用
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——10. 重写equals方法时遵守通用约定
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- LCA模板(数剖实现)
题目链接:https://www.luogu.org/problemnew/show/P3379 题意:LCA模板题. 思路:今天开始学树剖,先拿lca练练.树剖解lca,两次dfs复杂度均为O(n) ...
- Git的常见操作
1.git init:初始化 git status:查看版本状态 git log : 查看提交日志
- Spring(十一)-- Spring代理生成器
Spring代理生成器 1.创建需要的dao接口 2.创建需要的daoImpl实现类 3.创建前置增强类 4.创建spring.xml文件 <!-- 这个案例 需要解决的问题: 一个代理工厂 配 ...
- CentOS 7 安装ActiveMQ
今天给大家介绍一下 CentOS 7 下如何安装ActiveMQ,每个步骤均为亲自己操作后记录.下面我们开始吧. 1.首先进入自己的目录下面,创建并进入 activeMQ 文件夹(mkdir acti ...
- leecode刷题(24)-- 翻转二叉树
leecode刷题(24)-- 翻转二叉树 翻转二叉树 翻转一棵二叉树. 示例: 输入: 4 / \ 2 7 / \ / \ 1 3 6 9 输出: 4 / \ 7 2 / \ / \ 9 6 3 1 ...
- MVC 部署到服务器
1.Nuget程序包管理 —>程序包管理控制台,运行以下命令即可:Update-Package Microsoft.AspNet.WebApi -reinstall 2.dll文件的缺少,覆盖
- Hadoop学习之 HIVE 多用户模式安装
一.启动hadoop 集群 1.启动zookeeper 集群 zkServer.sh start 2.在master.hadoop 机器上 ./start-all.sh 由于 start-all命 ...
- PHP进阶书籍
[初阶](基础知识及入门) 01. <PHP与MySQL程序设计(第4版)> http://item.jd.com/10701892.html 02. <深入浅出MySQL 数据库 ...
- js之运算符(算术运算符)
Javascript中的运算符大多是由标点符号少数由关键字表示.可以根据其操作数的个数进行分类.大多数运算符是一个二元运算符,将两个表达式合成一个比较复杂的表达式.还有需要注意的一点是运算符的优先级, ...
- ubuntu修改apt源
1.修改之前首先做好备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 2.编辑源列表文件 sudo vim /etc/apt/sou ...