问题聚焦:

已经对一个对象执行了delete语句,还会发生内存泄漏吗?

先来看个demo:

// 计时器类
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
};
class AtomicClock: public TimeKeeper { ...... }; // 原子钟
class WaterClock: public TimeKeeper { ...... }; // 水表
class WristWatch: public TimeKeeper { ...... }; // 腕表 // 设计工厂函数以供用户使用
TimeKeeper* ptk = getTimeKeeper(); // Factory函数会“返回一个父类的指针,指向新生成的子类对象”
......
delete ptk; // Point! 释放它,避免资源泄漏

上面的这个demo有什么问题呢?
内存泄漏?后面已经delete掉这个对象了,还会内存泄漏吗?答案是肯定的。
让我们分析一下。
问题描述:getTimeKeeper()函数返回的指针指向一个derived class对象,而那个子类对象经由它的父类指针被释放,而它的父类有个non-virtual析构函数。
导致结果:诡异的“局部销毁”
                  C++指出,当子类对象经由一个它的父类对象指针被删除,而该父类对象的析构函数为non-virtual,其结果是:通常情况下,该对象的父类部分被销毁,而子类部分没有被销毁。
解决方案:父类的析构函数声明为virtual函数。
Demo:

class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
......
}; // 使用
TimeKeeper* ptk = getTimeKeeper();
....
delete ptk;

这样看来,以后我们定义一个类的时候,就把它的析构函数全部声明为virtual函数,可以避免“局部销毁”问题。
但是这更不是一个好主意。(PS: 感谢我的老师让我知道了虚函数表这个东东.....)
还是先来看一个demo.

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

如果int占用32bits,那么Point对象可塞入一个64bit缓存器中。这样一个Point对象可被当作一个“64bit量”传给以其他语言如C或Fortran撰写的函数。
但是如果这里的析构函数被声明为virtual,会引起什么影响呢?
        virtual关键字可以在运行期决定哪一个virtual函数被调用,这个强大的功能显然要付出代价的。这个代价就是需要额外的空间存储虚函数表——编译器在其中寻找适当的函数指针,以及指向其中的指针(存储在对象中)。(这里不讨论虚函数表的实现细节)
        所以,如果将析构函数声明为virtual,Point对象的体积就会增大:在32bit计算机体系结构中将占用64bits到96bits(加上虚函数指针32bits)。因此,添加一个虚函数会使得这个对象增大50%~100%。C++的该对象也就无法和C里的该对象兼容了,如果不明确补偿,那么两者就无法兼容了。
总结一句话就是:盲目地将所有类的析构函数声明为virtual,或者non-virtual都是错误的。
需要格外注意的一点是:不要企图继承一个标准容器或者其他“带有non-virtual析构函数”,虽然看起来很方便。就像下面做的这样:

class SpecialString: public std::string {
......
}; // 如果你有一段代码这样写,绝对是你悲剧的开始
SpecialString* pss = new SpecialString("Hello world!");
std::string* ps;
......
ps = pss;
......
delete ps; // 局部销毁,发生了资源泄漏

如果你确定这个类是当作一个父类来使用的话,声明一个抽象类或许是一个不错的主意。
来看一个demo

class AWOV {
public:
virtual ~AWOV() = 0;
};
AWOV::~AWOV() {} //纯虚函数的定义

这里有一个需要注意的地方是,这个析构函数的定义是必须的,不然编译器会报错。(因为编译不会再为你默默的生成一个了)
小结:
  • 带有多态性质的父类应该声明一个virtual析构函数,如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
  • 如果一个类的不是设计为一个父类来使用,或不是为了具备多态性,就不应该声明virtual析构函数,当然,不要有继承它的类出现。

Effective C++(7) 为多态基类声明virtual析构函数 or Not的更多相关文章

  1. [Effective C++ --007]为多态基类声明virtual析构函数

    引言: 我们都知道类的一个很明显的特性是多态,比如我们声明一个水果的基类: class Fruit { public: Fruit() {}; ~Fruit(){}; } 那么我们根据这个Fruit基 ...

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

    一个函数的返回值为基类指针,而当指针指向一个派生类对象,接下来派生类对象被这个基类指针删除的时候,就出现了局部销毁的问题.因为C++指出,当派生类经由一个基类指针被删除,而该基类指针带着一个non-v ...

  3. effective c++(07)之为多态基类声明virtual析构函数

    class TimeKeeper { public: TimeKeeper() ; ~TimeKepper() ; ... } ; class AtomicClock:public TimeKeepe ...

  4. Effective C++_笔记_条款07_为多态基类声明virtual析构函数

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 这个规则只适用于polymorphic(带多态性质的)base ...

  5. Effective C++ -----条款07:为多态基类声明virtual析构函数

    polymorphic(带多态性质的)base classes应该声明一个virtual析构函数.如果class带有任何virtual函数,它就应该拥有一个virtual析构函数. Classes的设 ...

  6. 【C++】为多态基类声明virtual析构函数

    来自<Effective C++>条款07:为多态声明virtual析构函数 当derived class对象经由一个base class指针被删除,而该base class带着一个non ...

  7. 条款7:为多态基类声明virtual析构函数

    C++明确指出:当派生类对象是由一个基类指针释放的,而基类中的析构函数不是虚函数,那么结果是未定义的.其实我们执行时其结果就是:只调用最上层基类的析构函数,派生类及其中间基类的析构函数得不到调用. # ...

  8. NO.6: 为多态基类声明virtual析构函数

    注意:polymorphic base class 应该具有虚析构函数,如果class带有任何virtual函数,也应具有虚析构函数 class不具备polymorphic属性则不应该声明virtua ...

  9. Effective C++学习笔记 条款07:为多态基类声明virtual析构函数

    一.C++明确指出:当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未定义——实际执行时通常发生的是对象的 ...

随机推荐

  1. unity批量设置图片为etc2格式或者astc格式

    网上找了半天,没一个能用的,干脆自己写个,直接拷贝这个脚本就行 这个是ios版本的,安卓的话写在注释里面,去掉注释就能用了 现在ios支持一种新格式叫astc比原本的pvrtc压缩比更高,而且质量更高 ...

  2. 2-6 js基础-ajax

    1.var oAjax=new XmlHttpRequest()//创建一个ajax对象,兼容非ie6 var oAjax=new ActiveXObject('Microsoft.XMLHTTP') ...

  3. 10种jquery选择器操作详解(转)

    jquery选择器大体上可分为4 类: 1.基本选择器2.层次选择器3.过滤选择器4.表单选择器 其中过滤选择器可以分为:1.简单过滤选择器2.内容过滤选择器3.可见性过滤选择器4.属性过滤选择器5. ...

  4. postgresql 备份与恢复

    备份 C:\PostgreSQL\\bin>pg_dump -h localhost -p -U postgres Mes > d:/.bak 恢复 C:\PostgreSQL\\bin& ...

  5. prism.js使页面代码变得漂亮

    第一次接触prism.js,并把它用到了园子里. 装逼如风,常伴吾身.有了如此利器,从此院子里我的代码是"最"漂亮的! 身为程序员深刻体会代码高亮在生产过程中是多么的重要.以下便是 ...

  6. 删除Panl控件中窗体的方法

    //删除窗体方法 private void CloseFrm() { foreach (Control item in panel1.Controls) { if (item is Form) //判 ...

  7. HTML页面格式

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  8. Git基础--笔记

    0.取的项目的git仓库 有两种取得 Git 项目仓库的方法.第一种是在现存的目录下,通过导入所有文件来创建新的 Git 仓库. 第二种是从已有的 Git 仓库克隆出一个新的镜像仓库来 1.在工作目录 ...

  9. MySql的InnoDB存储引擎--索引

    索引分类: 1.聚集索引:索引顺序与物理顺序一致. MySql 的 InnoDB 中,主键索引就是聚集索引.好处是,进行搜索的时候,因为索引和物理顺序一致,所以找数据的时候更快. 2.非聚集索引:索引 ...

  10. mac mamp环境 PHP命令行反应缓慢解决

    在hosts增加下面两项即可. 原因:尝试执行DNS查找本地计算机的主机名的原因 raydeMacBook-Pro.local 就是你的MAC名称   127.0.0.1 raydeMacBook-P ...