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


    问题聚焦:
    已经对一个对象执行了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析构函数,当然,不要有继承它的类出现。




  • 相关阅读:
    RH133读书 笔记(3)
    RH033读书笔记(16)-Lab 17 Installation and Administration Tools
    RH033读书笔记(15)-Lab 16 The Linux Filesystem
    RH033读书笔记(14)-Lab 15 Switching Users and Setting a Umask
    RH033读书笔记(13)-Lab 14 Network Clients
    RH033读书笔记(12)-Lab 13 Finding and Processing Files
    RH033读书笔记(11)-Lab 12 Configuring the bash Shell
    RH033读书笔记(10)-Lab 11 Process Control
    RH033读书笔记(9)-Lab 10 Understanding the Configuration Tools
    RH033读书笔记(8)-Lab 9 Using vim
  • 原文地址:https://www.cnblogs.com/suzhou/p/3638965.html
Copyright © 2020-2023  润新知