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

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

  例如:

class base
{
    public:
        base(const string& name): m_baseName(name) {}
    private:
        string m_baseName;
};

class derived: public base
{
    public:
        derived(const string& baseName, const string& derivedName): base(baseName), m_derivedName(derivedName) {}
    private:
        string m_derivedName;
};
int main(void)
{
    base *pd = new derived("base", "derived");
    delete pd;
}

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

  gdb调试如下:

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

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

  例如:

class base
{
    public:
        ~base()
        {
            cout << "base::~base" << endl;
        }
        virtual void f1()
        {
            cout << "base::f1()" << endl;
        }
        void f2()
        {
            cout << "base::f2()" << endl;
        }
};

class derived: public base
{
    public:
        virtual void f1()
        {
            cout << "derived::f1()" << endl;
        }

        void f2()
        {
            cout << "derived::f2()" << endl;
        }

        ~derived()
        {
            cout << "derived::~derived()" << endl;
        }
};
int main(void)
{
    derived d;
    base &rd = d;
    rd.f1();
    rd.f2();
}

  输出为:

derived::f1()
base::f2()
derived::~derived()
base::~base

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

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

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

class base
{
    public:
        base(const string& name): m_baseName(name) {}
        virtual ~base() {}
    private:
        string m_baseName;
};

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

  gdb单步调试验证如下:

Breakpoint 1, main () at clause07_2.cpp:31
31        base *pd = new derived("base", "derived");
(gdb) n
32        delete pd;
(gdb) s
derived::~derived (this=0x603070, __in_chrg=<optimized out>) at clause07_2.cpp:20
20    class derived: public base
(gdb) s
derived::~derived (this=0x603070, __in_chrg=<optimized out>) at clause07_2.cpp:20
20    class derived: public base
(gdb) s
base::~base (this=0x603070, __in_chrg=<optimized out>) at clause07_2.cpp:15
15            virtual ~base() {}
(gdb) s
main () at clause07_2.cpp:33
33    }

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

  例如:

class base
{
    public:
        virtual void f1() {}
};

class derived: public base
{
    public:
        virtual void f1()
        {
            ptr = operator new(sizeof(int));
        }
        ~derived()
        {
            delete ptr;
        }
    private:
        void* ptr;
};
int main(void)
{
    base *pd = new derived();
    pd->f1();
    delete pd;
}

  虚函数机制使调用派生类函数分配了内存,希望在派生类的析构函数里释放,但是由于析构函数不是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析构函数。

时间: 2024-10-18 00:33:49

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

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

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 这个规则只适用于polymorphic(带多态性质的)base class身上.这种base class的设计目的是为了用来“通过base class接口处理derived class对象”.假如我们在程序中设计factory(工厂)函数,让它返回base class指针,指向新生成的derived class对象,假设base class有个non-virtu

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

class TimeKeeper { public: TimeKeeper(); // ~TimeKeeper(); 错误,此作为一个基类,被继承了.其继承类被delete后,基类被销毁,但继承类可能没被销毁 virtual ~TimeKeeper();//必须声明为virtual类型才可以. protected: private: };   class AtomicClock: public TimeKeeper{}; //继承   TimeKeeper* ptk = getTimeKeepe

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

1. 如果一个类将要作为基类,那么它应该具有一个虚析构函数以防止delete经由基类指针指向的派生类对象时发生的"局部销毁"问题,避免造成内存泄露,数据结构败坏,在调试器上浪费时间等问题;任何带有虚函数的类几乎肯定需要一个虚析构函数. 2. 在不必需的情况下不应该将成员函数设为虚函数,否则,如果导致一个本来没有虚函数的类带有虚函数,那么它就会增加一个隐含的vptr(virtual table pointer)指针(指向虚函数表用于支持动态绑定),在这种情况下,类的"体积&qu

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

1.假设有如下时间类: class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); }; class AtomicClock :public TimeKeeper {}; factory函数(用户只是想调用时间,而不想操心时间如何计算等细节):TimeKeeper *getTimeKeeper() //返回一个base class指针,并指向新生成的derived class TimeKeeper *a = getTimeKeeper();//

Effective C++ Item 07-为多态基类声明virtual析构函数

Item 07-为多态基类声明virtual析构函数 Declare destructors virtual in polymorphic base class why?  http://blog.csdn.net/wxqian25/article/details/7361069 Ex: 记录时间有多种做法,一个基类和一些派生类 class TimeKeeper{                        //    base class 基类 public: TimeKeeper(); ~

effective c++ 条款07:为多态基类声明virtual析构函数

记住:带多态性质的基类应该声明一个virtual析构函数.如果class带有任何virtual函数,它就应该拥有一个virtual析构函数.class的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数. class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); ... }; class AtomicClock: public TimeKeeper { ... }; class WaterClock: pub

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

C++明确指出:当派生类对象是由一个基类指针释放的,而基类中的析构函数不是虚函数,那么结果是未定义的.其实我们执行时其结果就是:只调用最上层基类的析构函数,派生类及其中间基类的析构函数得不到调用. 1 #include <iostream> 2 3 using namespace std; 4 5 class TimeKeeper 6 { 7 public: 8 TimeKeeper(); 9 ~TimeKeeper(); 10 }; 11 TimeKeeper::TimeKeeper() 1

effective c++ 条款07(为多态基类声明virtual析构函数)整理

一.虚函数表原理 陈皓的一篇blog讲的很透彻:http://blog.csdn.net/haoel/article/details/1948051/ 虚函数表可以分为:单一继承无虚函数覆盖.单一继承有虚函数覆盖.多重继承无虚函数覆盖和多重继承和有虚函数覆盖. 注意与虚拟继承区分开来 二.多态实现原理 多态则是通过继承.虚函数(virtual).指针来实现. class A { public: virtual void func() const { coust << "A::func

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

引言: 我们都知道类的一个很明显的特性是多态,比如我们声明一个水果的基类: class Fruit { public: Fruit() {}; ~Fruit(){}; } 那么我们根据这个Fruit基类可以派生出以下的子类: class Apple:public Fruit{}; class Orange:public Fruit{}; 那么问题来了,如果我们想经由一个基类指针去删除一个派生类Apple,且我们有以下的方法 Fruit * foo(){ Apple * p = new Apple