zoukankan      html  css  js  c++  java
  • 条款7:为多态基类声明virtual析构函数

    多态基类增加一个virtual的析构函数

    现在需要一种设计,要设计一个类记录时间,但是记录时间的方式有很多,可以通过手机MobileClock,可以通过水钟WaterClock等。

    所以要这种实现的方式:

    class TimeKeeper
    {
    public:
    	TimeKeeper();
    	~TimeKeeper();
    };
    
    class MobileClock : public TimeKeeper
    {
    public:
    	MobileClock();
    	~MobileClock();
    };
    
    class WaterClock : public TimeKeeper
    {
    public:
    	WaterClock();
    	~WaterClock();
    };
    

      

    这种设计的方式是工厂factory的方式,利用了C++多态的方式。利用父类的指针TimeKeeper*指向任何子类的对象就可以实现了,这样的方式很容易维护和功能的扩展。比如有一天我想扩展计时的方式,我想用原子钟计时,增加一个AtomicClock类,利用这种设计能改动最少的代码实现功能的扩展。

    所以工厂函数是这样的:

    TimeKeeper *ptk = getTimeKeeper();
    

      被getTimeKeeper返回的这个对象应该位于内存的heap存储区,为了避免内存泄露,程序必须将factory返回的每一个对象在合适的时候用delete释放掉。

    所以代码是这样的:

    TimeKeeper *ptk = getTimeKeeper();
    	
    //TODO:这里用ptk做一些操作,比如显示时间等
    
    delete ptk;
    

      这样的操作表面上看似乎有点合理,但是我们会发现在通过释放delete对象的时候,基类指针ptk要调用一个析构函数。又由于基类(TimeKeeper)的析构函数是non-virtual的,是静态绑定的。所以这里通过ptk指针调用的是基类TimeKeeper的析构函数。那么这个基类的析构函数能把整个派生类对象释放掉吗?答案是不能。它只能释放派生类对象中的基类的成分,并不能释放专属于派生类的那部分空间。于是就造成了一个诡异的“局部销毁”对象。

    解决这个问题的方法很简单:把基类的析构函数做成virtual的,这样的话通过基类的指针ptk调用一个析构函数的时候,就会动态绑定,根据ptk实际指向的对象调用这个对象的析构函数。这样就自动的调用到了派生类的析构函数。通过派生类的析构函数就能如愿的释放掉派生类对象,同时也能够释放掉基类的成分

    class TimeKeeper
    {
    public:
    	TimeKeeper();
    	virtual ~TimeKeeper();
    };
    

      注意这里说的为析构函数增加virtual关键字针对的是多态基类而言的。这种类都是用来派生用的,同时还会有其他的virtual函数,如virtual getCurrentTime,然后在不同的派生类中在定制它们自己的getCurrentTime。所以总而言之,任何用作多态基类的class都会有一些virtual的函数,这时为这个多态基类定义一个virtual的析构函数是很必要的。

    多态基类和virtual函数是一起出现的,一个类只要有virtual函数,那么这个类一定是要打算作为多态基类使用的,所以要有一个virtual的析构函数。换而言之,如果一个类是作为多态基类设计的,那么它至少有一个virtual函数,那么这时析构函数也要声明为virtual。

    所以这一条的规则是为多态基类声明一个virtual的析构函数。但是反过来,如果一个库文件中的类并没有设计成基类的意图,千万不要去继承它,不然当你对这个基类使用多态的时候,就会发生上面的“对象局部销毁”的现象发生。(因为你不能修改一个库文件中的类)

    非多态基类的类禁止virtual的析构函数

    考虑一个二维空间的坐标点的class:

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

      这样的类在设计的时候都没有把他当做基类的意图,你如果硬是要给它的析构函数加上一个virtual,那么这个class的实例就会占用更多的空间,因为除了两个int变量外,还有在对象的开始的部分存放该对象的虚函数表的指针,表明这个对象的虚函数表在内存的地址。这样的话如果把这个类给C语言或者其他语言,大小就会不对应,除非明确的补偿虚函数表指针占用的空间,才能具有移植性。

    所以,对于那些非多态基类的类,不要把其析构函数声明为virtual的。

    非多态的基类也要禁止virtual析构函数

    并非所有的基类的设计目的都是为了多态用途,例如标准string和STL容器都不被设计作为基类使用,更别提多态了。某些class的设计目的是作为基类使用,但不是为了多态,它们并不需要“父类的指针指向子类的对象”做一些操作。所以也不需要声明virtual的析构函数。

    总结

    上面的规则其实很简单:

    为多态基类声明一个virtual的析构函数。
    
    如果一个类不是用作基类的用途,不要把它的析构函数声明为virtual;
    
    如果一个类是作为基类的用途,同时需要“父类的指针指向子类的对象”做一些操作,那么要声明一个virtual的析构函数;
    
    如果一个类是作为基类的用途,但是不需要多态操作子类对象,不要声明一个virtual的析构函数。
    
    最后一句:多态基类和virtual函数是一起出现的,谁离开了谁都玩不了。
    

      

  • 相关阅读:
    oracle 存储过程深入学习与应用
    程序猿-技术成长-理论方法-实践指导
    PowerDesigner 概念数据模型(CDM)
    关于oracle监听程序的相关问题及解决方法
    代码调试的专业姿势
    新年学习目标,了解所有框架,熟悉部分常用框架,熟练使用其中三个
    ResultSet 处理方法
    JPA 相关API (一)
    从输入 URL 到页面加载完的过程中都发生了什么事情?
    R语言中的if-else语句写法
  • 原文地址:https://www.cnblogs.com/stemon/p/4632981.html
Copyright © 2011-2022 走看看