现在的位置: 首页 > 综合 > 正文

容器与析构函数调用

2013年10月06日 ⁄ 综合 ⁄ 共 2572字 ⁄ 字号 评论关闭

#include <iostream>
#include <cstdlib>
#include <vector> 
using   namespace   std; 

class   CDemo   { 
public: 
    CDemo():str(Null){}; 
    ~CDemo()  
    {  
        if(str)   delete[]   str;  
    }; 
    char*   str; 
}; 

int   main(int   argc,   char**   argv)   { 
    CDemo   d1; 
    d1.str=new   char[32]; 
    strcpy(d1.str, "trend   micro"); 

    vector<CDemo>   *a1=new   vector<CDemo>(); 
   
    a1->push_back(d1);
    delete   a1;
  
    return EXIT_SUCCESS;
}

这个程序在退出时,会出问题,什么问题?重复delete同一片内存,程序崩溃。
我们把析构函数改为如下,可以更清楚的看到这一点:
    ~CDemo() 
    { 
        if(str)
        {
            static int i=0;
            cout<<"&CDemo"<<i++<<"="<<(int*)this<<",    str="<<(int *)str<<endl;
            delete[]   str;    
        }
    };
  
运行时我们发现打印如下信息:
&CDemo0=000309D8,       str=000307A8
&CDemo1=0013FF70,       str=000307A8
也就是说,发生了CDemo类的两次析构,两次析构str所指向的同一内存地址空间(两次str值相同=000307A8)。

上面的代码会两次调用CDemo的析构函数。在C++ Primer上说,不需要调用delete a1,否则容易造成两次析构。但是a1是new出来的,一般new出来的都必须要手动调用delete去释放内存。

所以这里有点不是很明白。后来看到网上有一种观点,觉得挺有道理的。是这样解释的。代码执行到delete a1时候,vector调用了对象的析构函数~CDemo(){if(str) delete [] str;},这时已经把d1.str = new char[32]给释放掉了,由于没有定义拷贝构造函数实现深拷贝,当代码执行完毕的时候,CDemo d1对象还会自己析构一次,又会调用~CDemo(){if(str) delete [] str;}一次,这个时候会出现重复delete,所以会出错。如果定义了拷贝构造函数实现深拷贝
CDemo(const CDemo &cd)
{
     this->str = new char[strlen(cd.str)+1];
    strcpy(str, cd.str);
}
函数在执行到a1->push_back(d1),d1的副本会在堆栈中申请另外的内存,而不是直接指向d1.str所指向的内存。这时代码执行到delete a1的时候vector调用了对象的析构函数~CDemo(),此时delete掉的是副本里申请的内存(深拷贝实现),当代码执行完毕时需要析构CDemo d1的时候,delete掉的是d1.str = new char[32],没有重复delete。

如果vector改为vector<CDemo *> *a1 = new vector<CDemo *>();即存储类指针,那么在执行delete a1之前,还要手工去删除vector中的每个元素。

---------------------------------------------------------------------------------------------------------------------------

那到底那里错误?

这句a1的声明和初始化语句“vector<CDemo>   *a1=new   vector<CDemo>();  ”说明a1所含元素是“CDemo”类型的,在执行“a1->push_back(d1);  ”这条语句时,会调用CDemo的拷贝构造函数,虽然CDemo类中没有定义拷贝构造函数,但是编译器会为CDemo类构建一个默认的拷贝构造函数(浅拷贝),这就好像任何对象如果没有定义构造函数,编译器会构建一个默认的构造函数一样。

正是这里出了问题。a1中的所有CDemo元素的str成员变量没有初始化,只有一个四字节(32位机)指针空间。
“a1->push_back(d1);”这句话执行完后,a1里的CDemo元素与d1是不同的对象,但是a1里的CDemo元素的str与d1.str指向的是同一块内存,这从后来的打印信息就可以看出来。

我们知道,局部变量,如“CDemo   d1;  ” 在main函数退出时,自动释放所占内存空间,
那么会自动调用CDemo的析构函数“~CDeme”,问题就出在这里。

前面的“delete   a1;”已经把 d1.str 释放了(因为a1里的CDemo元素的str与d1.str指向的是同一块内存),main函数退出时,又要释放已经释放掉的 d1.str 内存空间,所以程序最后崩溃。

 

解释清楚了。
这里最核心的问题归根结底就是浅拷贝和深拷贝的问题。如果CDemo类添加一个这样的拷贝构造函数就可以解决问题:
    CDemo(const   CDemo   &cd)
    {
        this->str   =   new   char[strlen(cd.str)+1];
        strcpy(str,cd.str);
    };
这就是深拷贝。

或者这样用:
    vector<CDemo*>   *a1=new   vector<CDemo*>();
    a1->push_back(&d1);
那么在    “delete   a1;” a1释放,同时a1里面包含的元素(”CDemo*“类型,仍然是一个指针,4字节空间)。
详细出处参考:http://www.itqun.net/content-detail/70928_2.html

【上篇】
【下篇】

抱歉!评论已关闭.