zoukankan      html  css  js  c++  java
  • c++ virtual 虚析构函数 资源释放的讨论

    关于c++ virtual,析构函数的讨论已经挺多了,参见

    http://zxjgoodboy.blog.sohu.com/61482463.html

    http://blog.csdn.net/zoukh/article/details/16624

    http://blog.csdn.net/han_348154920/article/details/5944351

    http://hi.baidu.com/bystander1983/blog/item/d61627553e3afe52d10906d2.html

    http://www.cnblogs.com/powersun/archive/2007/08/13/853015.html

    http://www.cnblogs.com/xd502djj/archive/2010/09/22/1832912.html

    但是这些文章大多在讨论:通过虚析构函数,可以利用多态顺利释放资源,防止内存泄露

    但是我在使用时却恰恰发生了相反的事情,不是内存泄漏,而是内存重复删除。这也是一个需要深思的问题。

    我们还是以传统的animal和dog为例。

    首先是基类 animal

    #pragma once
    class animal
    {
    public:
    animal(
    void);
    virtual ~animal(void);

    int * type;

    virtual void allocate(int n);
    virtual void release();

    };

    实现

    #include "animal.h"
    #include
    "stdio.h"
    animal::animal(
    void):type(0)
    {
    }
    animal::
    ~animal(void)
    {
    release();
    }

    void animal::allocate( int n )
    {
    release();
    puts(
    "allocate from Animal start!");
    type
    =new int[n];
    puts(
    "allocate from Animal end!");
    }

    void animal::release()
    {
    puts(
    "release from Animal start!");
    if(type)
    {
    delete [] type;
    }
    puts(
    "release from Animal end!");
    }
    .

    .

    派生类dog

    #pragma once
    #include
    "animal.h"
    class dog :
    public animal
    {
    public:
    dog(
    void);
    virtual ~dog(void);

    int * country;

    virtual void allocate(int n);
    virtual void release();
    };

    .

    实现

    #include "dog.h"
    #include
    "stdio.h"

    dog::dog(
    void):country(0)
    {}
    dog::
    ~dog(void)
    {}
    void dog::allocate(int n)
    {
    puts(
    "allocate from Dog start!");
    animal::allocate(n);
    country
    =new int[n];
    puts(
    "allocate from Dog end!");
    }
    void dog::release()
    {
    puts(
    "release from Dog start!");
    animal::release();
    if(country)
    {
    delete []country;
    }
    puts(
    "release from Dog end!");
    }

    .

    .

    main里考察分别使用基类和派生类指针的情形

    #include "dog.h"
    #include
    "stdio.h"
    int main()
    {
    animal
    * a=new dog();
    a
    ->allocate(12);
    delete a;
    puts(
    "\na finished!\n");

    dog
    *b=new dog();
    b
    ->allocate(12);
    delete b;
    puts(
    "\nb finished!\n");
    }
    .

    运行结果是

    allocate from Dog start!
    release from Dog start
    !
    release from Animal start
    !
    release from Animal end
    !
    release from Dog end
    !
    allocate from Animal start
    !
    allocate from Animal end
    !
    allocate from Dog end
    !
    release from Animal start
    !
    release from Animal end
    !

    a finished
    !

    allocate from Dog start
    !
    release from Dog start
    !
    release from Animal start
    !
    release from Animal end
    !
    release from Dog end
    !
    allocate from Animal start
    !
    allocate from Animal end
    !
    allocate from Dog end
    !
    release from Animal start
    !
    release from Animal end
    !

    b finished
    !

    .

    两种情形是一致的,但是要注意对于单一情形,在allocate和析构函数中,都是在基类里调用了release(),但表现的结果确完全不同:

    allocate成功使用了多态,析构函数却只是调用了animal::release()

    产生这种问题的根源在于:派生类的普通成员函数会对基类的同签名函数产生隐藏,调用派生类的函数不会触发基类的函数;但是析构函数就恰恰相反,调用派生类的析构函数后会紧接着调用基类的析构函数

    也就是说,我在dog::allocate()中显式的调用animal::allocate(),也就顺次显式调用release(),实际上使用派生类指针调用基类函数,恰好满足多态条件,因此运行正常。

    但是在dog::~dog()中,我没用显示调用基类析构函数,而是系统隐式调用的;但是系统隐式调用时,派生类已经析构了,它是按照animal::~animal()来调用的,那么release()也就只是有基类指针使用,因此没有使用多态,animal发生了内存泄露。

    那么,我如果在dog::~dog()中显式的调用release()行不行呢?

    我们试试看

    dog::~dog(void)
    {
    release();
    }

    现在的程序输出是

    allocate from Dog start!
    release from Dog start
    !
    release from Animal start
    !
    release from Animal end
    !
    release from Dog end
    !
    allocate from Animal start
    !
    allocate from Animal end
    !
    allocate from Dog end
    !
    release from Dog start
    !
    release from Animal start
    !
    release from Animal end
    !
    release from Dog end
    !
    release from Animal start
    !

    没错,就是这些!程序就在这卡着。(我的开发环境是 vs2010+win7)

    尝试调试,说程序陷入死锁状态。仔细观察最后5句,发现 dog::~dog()里的release()顺利执行,在调用基类的析构函数时卡住了。类似的情况我在另一个大点的项目中则是直接崩溃了。

    分析一下这里原因也好理解,animal::release()函数被调用了两次,第二次调用时产生死锁或者崩溃。

    那么是我程序哪里实现的有问题么?

    allocate与release都应该是virtual的吧?

    dog::release()中必须要调用 animal::release()才合理吧?

    那么现在陷入了两难困境:dog::~dog()中到底应不应该调用release()呢?调用,程序崩溃;不调用,内存泄露。

    .

    .

    很让人头疼的问题。

    无奈,我只好曲线救国,把release拆分成了两部分来实现,总算可以实现目标,但这样确实太丑了。

    animal.h

    #pragma once
    class animal
    {
    public:
    	animal(void);
    	virtual ~animal(void);
    
    	int * type;
    
    	virtual void allocate(int n);
    	virtual void release();
    	void releaseMyData();
    
    };
    

    animal.cpp

    #include "animal.h"
    #include
    "stdio.h"
    animal::animal(
    void):type(0)
    {
    }
    animal::
    ~animal(void)
    {
    release();
    }

    void animal::allocate( int n )
    {
    release();
    puts(
    "allocate from Animal start!");
    type
    =new int[n];
    puts(
    "allocate from Animal end!");
    }

    void animal::release()
    {
    releaseMyData();
    }

    void animal::releaseMyData()
    {
    puts(
    "release from Animal start!");
    if(type)
    {
    delete [] type;
    }
    puts(
    "release from Animal end!");
    }

    dog.h

    #pragma once
    #include
    "animal.h"
    class dog :
    public animal
    {
    public:
    dog(
    void);
    virtual ~dog(void);

    int * country;

    virtual void allocate(int n);
    virtual void release();
    void releaseMyData();
    };

    dog.cpp

    #include "dog.h"
    #include
    "stdio.h"

    dog::dog(
    void):country(0)
    {}


    dog::
    ~dog(void)
    {
    releaseMyData();
    }

    void dog::allocate(int n)
    {
    puts(
    "allocate from Dog start!");
    animal::allocate(n);
    country
    =new int[n];
    puts(
    "allocate from Dog end!");
    }
    void dog::release()
    {
    animal::release();
    releaseMyData();
    }

    void dog::releaseMyData()
    {
    puts(
    "release from Dog start!");
    if(country)
    {
    delete []country;
    }
    puts(
    "release from Dog end!");
    }

    你聪明的,告诉我,我哪里错了?有什么解决方案啊?

  • 相关阅读:
    安卓开发之有序广播
    安卓开发之无序广播
    安卓开发之短信发送器的开发
    安卓开发之隐式意图与显示意图
    安卓root权限 被提示Read-only file system 或 Operation not permitted 解决方案
    gcc 的基本使用和静态库、动态库的制作与使用
    Python类的学习,看这一篇文章就够了!
    Qt 添加程序图标和系统托盘图标
    Qt ListWidget item 发起拖放
    Qt 接受拖放
  • 原文地址:https://www.cnblogs.com/dabaopku/p/2097861.html
Copyright © 2011-2022 走看看