一、C++明确指出:当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未定义——实际执行时通常发生的是对象的derived成分没有被销毁!(注:使用基类引用派生类的方式使用多态,由于引用只是对原对象的一个引用或者叫做别名,其并没有分配内存,对其引用对象内存的销毁,将由原对象自己负责,所以使用引用的时候,如果析构函数不为virtual,那么也不会发生derived成员没有销毁的情况)

  例如:

  1. class base
  2. {
  3. public:
  4. base(const string& name): m_baseName(name) {}
  5. private:
  6. string m_baseName;
  7. };
  8.  
  9. class derived: public base
  10. {
  11. public:
  12. derived(const string& baseName, const string& derivedName): base(baseName), m_derivedName(derivedName) {}
  13. private:
  14. string m_derivedName;
  15. };
  1. int main(void)
  2. {
  3. base *pd = new derived("base", "derived");
  4. delete pd;
  5. }

  经由gdb单步调试发现,delete pd的时候,只调用了base的析构函数,销毁了基类的数据成员,但是没有调用派生类的析构函数,导致派生类的数据成员没有被销毁,造成一个诡异的“局部销毁”对象。这会导致资源泄漏。

  gdb调试如下:

  1. Breakpoint , main () at clause07_1.cpp:
  2. base *pd = new derived("base", "derived");
  3. (gdb) n
  4. 31 delete pd;
  5. (gdb) s
  6. base::~base (this=0x603070, __in_chrg=<optimized out>) at clause07_1.cpp:11
  7. 11 class base
  8. (gdb) s
  9. main () at clause07_1.cpp:32
  10. 32 }
  11. (gdb) s
  12. 0x00007ffff752576d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.

  注:使用基类引用派生类的方式使用多态,由于引用只是对原对象的一个引用或者叫做别名,其并没有分配内存,对其引用对象内存的销毁,将由原对象自己负责,所以使用引用的时候,如果析构函数不为virtual,那么也不会发生derived成员没有销毁的情况

  例如:

  1. class base
  2. {
  3. public:
  4. ~base()
  5. {
  6. cout << "base::~base" << endl;
  7. }
  8. virtual void f1()
  9. {
  10. cout << "base::f1()" << endl;
  11. }
  12. void f2()
  13. {
  14. cout << "base::f2()" << endl;
  15. }
  16. };
  17.  
  18. class derived: public base
  19. {
  20. public:
  21. virtual void f1()
  22. {
  23. cout << "derived::f1()" << endl;
  24. }
  25.  
  26. void f2()
  27. {
  28. cout << "derived::f2()" << endl;
  29. }
  30.  
  31. ~derived()
  32. {
  33. cout << "derived::~derived()" << endl;
  34. }
  35. };
  1. int main(void)
  2. {
  3. derived d;
  4. base &rd = d;
  5. rd.f1();
  6. rd.f2();
  7. }

  输出为:

  1. derived::f1()
  2. base::f2()
  3. derived::~derived()
  4. base::~base

  derived的析构,是有对象d的销毁产生的。由于rd只是一个引用,没有自己的内存,所以不需要销毁。此时就不会有资源的泄漏(这里的引用的对象是静态对象,自动会销毁)。

二、对于(1)出现的问题的解决方法:给base class一个virtual析构函数。

  仅仅将base class的析构函数定义为virtual,如下:

  1. class base
  2. {
  3. public:
  4. base(const string& name): m_baseName(name) {}
  5. virtual ~base() {}
  6. private:
  7. string m_baseName;
  8. };

  这样,derived class的析构函数,由于继承的原因,自动变为virtual,此时,通过delete指针,会触发多态机制,由于指针的动态类型是derived class,又派生类的析构函数是虚函数,所以调用derived class的析构函数,这样就会销毁派生类的数据成员,当派生类析构完成,自动会调用基类的析构函数,这样就没有资源的泄漏了。

  gdb单步调试验证如下:

  1. Breakpoint , main () at clause07_2.cpp:
  2. base *pd = new derived("base", "derived");
  3. (gdb) n
  4. delete pd;
  5. (gdb) s
  6. derived::~derived (this=0x603070, __in_chrg=<optimized out>) at clause07_2.cpp:
  7. class derived: public base
  8. (gdb) s
  9. derived::~derived (this=0x603070, __in_chrg=<optimized out>) at clause07_2.cpp:
  10. class derived: public base
  11. (gdb) s
  12. base::~base (this=0x603070, __in_chrg=<optimized out>) at clause07_2.cpp:
  13. virtual ~base() {}
  14. (gdb) s
  15. main () at clause07_2.cpp:
  16. }

三、任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。

  例如:

  1. class base
  2. {
  3. public:
  4. virtual void f1() {}
  5. };
  6.  
  7. class derived: public base
  8. {
  9. public:
  10. virtual void f1()
  11. {
  12. ptr = operator new(sizeof(int));
  13. }
  14. ~derived()
  15. {
  16. delete ptr;
  17. }
  18. private:
  19. void* ptr;
  20. };
  1. int main(void)
  2. {
  3. base *pd = new derived();
  4. pd->f1();
  5. delete pd;
  6. }

  虚函数机制使调用派生类函数分配了内存,希望在派生类的析构函数里释放,但是由于析构函数不是virtual,所以delete只会调用基类的析构函数。所以这里有内存泄漏!
  解决方法同样是base class的析构函数定义为virtual,这样就可以利用虚函数的多态机制了。

四、如果base class不含virtual函数,通常表示它并不意图被用作一个base class。当class不企图被当作base class,令其析构函数为virtual往往是个馊主意。

  因为当一个类有虚函数后,那么它的所以对象的内存布局中都会多包含一个vptr(虚函数表指针),指向vtbl(vritual table)。这样会使对象膨胀,由于其他语言没有vptr,所以这就导致了不再具有移植性。

五、对于继承任何析构函数不是virtual的类,如果通过指针使用多态机制,最后通过delete该指向derived类型的base类型指针销毁原derived对象,就会出现泄漏的问题。

  (注,通过引用的方式,引用对象的销毁,由其引用的对象自己负责(静态对象自动销毁,动态分配的需要delete pd(派生类的指针);)。但是如果你打算使用多态,就可能会用到使用指针的多态的方式,所以最好声明基类的析构为virtual)原因就是如(1)所说明的。

六、当你打算使用的多态的时候,base class一般应该有个virtual析构函数。另外此时,不要继承析构不是virtual的类。然而,并非所有的基类都是为了多态 ,对于不打算使用多态的情况,基类不需要virtual析构函数。

总结

a.带多态性质的base class应该声明一个virtual析构函数。
b.如果class带有virtual函数,它就应该拥有一个virtual析构函数。
c.对于一个class的设计目的不是为了作为一个base class使用,或者对于base class设计目的不是为了多态用途,就不该virtual析构函数。

Effective C++学习笔记 条款07:为多态基类声明virtual析构函数的更多相关文章

  1. 《effective C++》:条款07——为多态基类声明virtual析构函数

    在继承中,基类的析构函数需要定义为虚析构函数数否则: (1)当派生类对象经由一个base类指针删除时,而这个base类的析构函数不是虚函数时,其结果是未定义的. (2)这样做会导致derived类部分 ...

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

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

  3. Effective C++ 条款七 为多态基类声明virtual析构函数

    class TimeKeeper { public: TimeKeeper(); // ~TimeKeeper(); 错误,此作为一个基类,被继承了.其继承类被delete后,基类被销毁,但继承类可能 ...

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

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

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

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

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

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

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

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

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

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

  9. Effective C++(7) 为多态基类声明virtual析构函数 or Not

    问题聚焦: 已经对一个对象执行了delete语句,还会发生内存泄漏吗? 先来看个demo: // 计时器类 class TimeKeeper { public: TimeKeeper(); ~Time ...

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

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

随机推荐

  1. 微软职位内部推荐-SDE2 (Windows - Power)

    微软近期Open的职位: SDE2 (Windows - Power) Windows Partner Enablement team in Operating System Group is loo ...

  2. javascript之流程控制 和函数的容易忽略点

    1.流程控制 1> for in  仅用于 对象的遍历: var box={ "name":'小红', 'age':18, 'height':165 }; for(var b ...

  3. SharePoint 优化显示WebParts

    在开发sharepoint中,经常遇到需要自定义显示列表中的一部分作为导航的内容, 如公告栏,新闻链接,最新动态等.... 我们通常需要显示一个列表的标题,并且限制字符长度, 外加一些条件,如按创建的 ...

  4. Jenkins入门-转

    reference : http://www.cnblogs.com/itech/archive/2011/11/23/2260009.html 在网上貌似没有找到Jenkins的中文的太多的文档,有 ...

  5. python 行转列

    #encoding=utf- print '中国' #二维阵列变换 行转化成列,列转化成行 lista=[[,,],[,,],[,,],[,,]] #使用列表推导 listb=[[r[col] ])) ...

  6. hibernate中文乱码问题

    在学习hibernate的过程中,发现在往mysql数据库中插入数据的时候会报错. <property name="hibernate.connection.url"> ...

  7. 【转】2-SAT题集

    转自:http://blog.csdn.net/shahdza/article/details/7779369 [HDU]3062 Party1824 Let's go home3622 Bomb G ...

  8. 【POJ】【2987】Firing

    网络流/最大权闭合子图 胡伯涛论文里有讲…… sigh……细节处理太伤心了,先是count和ans输出弄反了,改过来顺序时又忘了必须先算出来ans!要是不执行一下Dinic的话count就无意义了…… ...

  9. java.lang.NullPointerException&com.cb.action.LoginAction.execute(LoginAction.java:48)

    今天做一个Spring和Struts的融合,通过bean注入后,程序一跑起来,就报这个错误: java.lang.NullPointerException com.cb.action.LoginAct ...

  10. 仪表盘 hostmap 新玩法让运维工作越玩越 high

    Cloud Insight 第13次新品发布会现在开始,首先非常感谢大家前来看我们的新功能发布会,下面我先给大家介绍一下新功能,之后有什么问题大家尽管问