不好驯服的析构函数

219 篇文章 ¥19.90 ¥99.00
本文通过一个C++类的例子,探讨了深拷贝的重要性,指出在拷贝构造函数和赋值运算符重载中正确处理指针变量以防止内存泄漏。文中提到,如果不使用引用传递参数,会导致无限递归和内存问题。同时,析构函数的误用也会导致已分配的内存被多次删除,从而引发程序错误。修复这些问题的关键在于使用引用并正确处理内存分配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先看一个深拷贝的例子


 
view plaincopy to clipboardprint?
01.#include <iostream>  
02.using namespace std;  
03.const int SIZE          = 9;  
04.const int DEFAULT_NUM   = 6;  
05.class A  
06.{  
07.private:  
08.    int *p;  
09.    int size;  
10.public:  
11.    A(A&);  
12.    A();  
13.    ~A();  
14.    void    set();      //设置A的各个元素的值  
15.    void    showEle();  //输出p的各个元素  
16.    void    showAdd();  //输出p的地址  
17.    int     *getP();  
18.    int     getSize();  
19.};  
20. 
21.A::A()  
22.{  
23.    p       = NULL;  
24.    size    = 0;  
25.}  
26. 
27.A::~A()  
28.{  
29.    delete []p;  
30.    p = NULL;  
31.    size = 0;  
32.}  
33. 
34.A::A(A &right)  
35.{  
36.    size = right.getSize();  
37.    p = new int[size];  
38. 
39.    memcpy(p,right.getP(), sizeof(int)*size);  
40.}  
41. 
42.void A::set()//设置A的各个元素的值  
43.{  
44.    p       = new int[SIZE];  
45.    size    = SIZE;  
46. 
47.    for(int n=0; n<size; n++)  
48.    {  
49.        p[n] = DEFAULT_NUM;  
50.    }  
51.}  
52. 
53.void A::showEle()  
54.{  
55.    for(int n=0; n<size; n++)  
56.    {  
57.        cout<<p[n]<<endl;  
58.    }  
59.}  
60. 
61.void A::showAdd()  
62.{  
63.    cout<<p<<endl;  
64.}  
65. 
66.int *A::getP()  
67.{  
68.    return p;  
69.}  
70. 
71.int A::getSize()  
72.{  
73.    return size;  
74.}  
75. 
76.int main()  
77.{  
78.    A a;  
79.    a.set();  
80.    a.showEle();  
81.    a.showAdd();  
82. 
83.    A b(a);  
84.    b.showEle();  
85.    b.showAdd();  
86. 
87.    return 0;  
88.} 
#include <iostream>
using namespace std;
const int SIZE   = 9;
const int DEFAULT_NUM = 6;
class A
{
private:
 int *p;
 int size;
public:
 A(A&);
 A();
 ~A();
 void set();  //设置A的各个元素的值
 void showEle(); //输出p的各个元素
 void showAdd(); //输出p的地址
 int  *getP();
 int  getSize();
};

A::A()
{
 p  = NULL;
 size = 0;
}

A::~A()
{
 delete []p;
 p = NULL;
 size = 0;
}

A::A(A &right)
{
 size = right.getSize();
 p = new int[size];

 memcpy(p,right.getP(), sizeof(int)*size);
}

void A::set()//设置A的各个元素的值
{
 p  = new int[SIZE];
 size = SIZE;

 for(int n=0; n<size; n++)
 {
  p[n] = DEFAULT_NUM;
 }
}

void A::showEle()
{
 for(int n=0; n<size; n++)
 {
  cout<<p[n]<<endl;
 }
}

void A::showAdd()
{
 cout<<p<<endl;
}

int *A::getP()
{
 return p;
}

int A::getSize()
{
 return size;
}

int main()
{
 A a;
 a.set();
 a.showEle();
 a.showAdd();

 A b(a);
 b.showEle();
 b.showAdd();

 return 0;
} 可能我写的有点乱,成员变量包括一个指针,其实是一个数组,想要深拷贝。
所以我们需要重载拷贝构造函数。
初学C++的时候,直接记住了参数是引用类型,当时对语言没有深入的理解(虽然现在也不深入),也就没有往心里去,这些天模拟STL,遇到了好多关于深拷贝的问题,而且这问题出的很怪异(我会在下午提出),因此想到了拷贝构造函数,所以在这里需要深究一下。
拷贝构造函数的参数是引用类型。
我们可以试想一下不是引用的情况,也就是说,拷贝构造函数是下边这个样子:


view plaincopy to clipboardprint?
01.A::A(A right)  
02.{  
03.    size = right.getSize();  
04.    p = new int[size];  
05. 
06.    memcpy(p,right.getP(), sizeof(int)*size);  
07.} 
A::A(A right)
{
 size = right.getSize();
 p = new int[size];

 memcpy(p,right.getP(), sizeof(int)*size);
} 参数传递的是一个对象,按值传递,那么这个对象就会被复制到right,复制的时候又会调用拷贝构造函数,那又会复制一个对象到第二次调用的这个拷贝构造函数里边,因此又需要第三次调用拷贝构造函数。。。想一下,是不是无限循环?
简单地说:传值需要调用拷贝构造函数,而不是引用类型的拷贝构造函数又需要调用拷贝构造函数。因此无限循环。


Ok,把引用的这个问题说明白,下边说一下我这几天遇到的这个问题:
请先看一段代码:
view plaincopy to clipboardprint?
01.#include <iostream>  
02.#include <vector>  
03.using namespace std;  
04. 
05.class A  
06.{  
07.private:  
08.    int *p;  
09.    int size;  
10.public:  
11.    void    setSize(int);  
12.    void    setP(int*);  
13.    int     getSize()   const;  
14.    int     *getP()     const;  
15.    void    fun();              //设置相应的值,为拷贝做准备  
16.    void    show();  
17.            ~A();               //析构函数  
18.            A();  
19.      
20.    A operator = (A right)  //重载的等号  
21.    {  
22.        int *rightP = right.getP();  
23. 
24.        size    = right.getSize();  
25.        p       = new int[size];  
26.        memcpy(p, rightP, size*sizeof(int));  
27. 
28.        return *this;  
29.    }  
30.      
31.;  
32.};  
33.void A::setSize(int newSize)  
34.{  
35.    size = newSize;  
36.}  
37. 
38.void A::setP(int *newP)  
39.{  
40.    p = newP;  
41.}  
42. 
43.void A::show()  
44.{  
45.    for(int n=0;n<size;n++)  
46.    {  
47.        cout<<p[n]<<endl;  
48.    }  
49.}  
50. 
51.int *A::getP() const 
52.{  
53.    return p;  
54.}  
55. 
56.int A::getSize() const 
57.{  
58.    return size;  
59.}  
60. 
61.A::A()  
62.{  
63.    p = NULL;  
64.    size = 0;  
65.}  
66. 
67.void A::fun()  
68.{  
69.    p = new int [9];  
70.    for(int n=0;n<9;n++)  
71.    {  
72.        p[n] = n+1;  
73.    }  
74.    size = 9;  
75.}  
76. 
77.//关键是下边的析构函数,如果进行delete操作,该程序就会出错  
78.A::~A()  
79.{  
80.    delete []p;  
81.    p = NULL;  
82.    size = 0;  
83.}  
84. 
85.int main()  
86.{  
87.    A a;  
88.    a.fun();  
89.    cout<<"a.show():"<<endl;  
90.    a.show();  
91. 
92.    A b;  
93.    b = a;  
94.    cout<<"b.show()"<<endl;  
95.    b.show();  
96. 
97.    cout<<"a.show()"<<endl;  
98.    a.show();  
99. 
100.    return 0;  
101.} 
#include <iostream>
#include <vector>
using namespace std;

class A
{
private:
 int *p;
 int size;
public:
 void setSize(int);
 void setP(int*);
 int  getSize() const;
 int  *getP()  const;
 void fun();    //设置相应的值,为拷贝做准备
 void show();
   ~A();    //析构函数
   A();
 
 A operator = (A right) //重载的等号
 {
  int *rightP = right.getP();

  size = right.getSize();
  p  = new int[size];
  memcpy(p, rightP, size*sizeof(int));

  return *this;
 }
 
;
};
void A::setSize(int newSize)
{
 size = newSize;
}

void A::setP(int *newP)
{
 p = newP;
}

void A::show()
{
 for(int n=0;n<size;n++)
 {
  cout<<p[n]<<endl;
 }
}

int *A::getP() const
{
 return p;
}

int A::getSize() const
{
 return size;
}

A::A()
{
 p = NULL;
 size = 0;
}

void A::fun()
{
 p = new int [9];
 for(int n=0;n<9;n++)
 {
  p[n] = n+1;
 }
 size = 9;
}

//关键是下边的析构函数,如果进行delete操作,该程序就会出错
A::~A()
{
 delete []p;
 p = NULL;
 size = 0;
}

int main()
{
 A a;
 a.fun();
 cout<<"a.show():"<<endl;
 a.show();

 A b;
 b = a;
 cout<<"b.show()"<<endl;
 b.show();

 cout<<"a.show()"<<endl;
 a.show();

 return 0;
} 可以试着运行一下,在我电脑上边出现了下边这个错误


很恐怖的错误。呵呵,让我们详细分析一下。
第一次a.show()成功,说明对象a建立没有问题;b.show()失败,说明没有复制成功;第二次a.show()失败,说明a被修改了,可以猜想一下,是不是被调用了析构函数?
现在重载 = 的时候是按值传递,那么,我们传进去的是地址p和size。
之后是执行拷贝的函数,这个没啥问题,把right中的相关数据拷贝。
然后就开始执行析构函数了,首先析构的是传进来的参数,也就是right,由于right的p和主函数中a的p相等,因此right的p被delete了也就是a的p被delete了,在这里,我们把主函数中的a不小心析构了一点点(把p给delete了)。因为函数的返回的是值,所以第二步析构的是(*this),这样,就把复制后的p给delete了。到这里,我们悲剧地把内存中所有的p都delete了。
所以,当再次输出a和b的时候,全都是随机值。

细心的朋友可能会发现,当第二次输出a和b的时候仍然是输出了10个,也就是开始的size值。a的size是10不难理解。由于析构函数是在函数返回后调用的,所以返回的b的size是不改变的(返回是在改变以前)。

我们试着改一下:
下边是我在参数上边加了&,这样,当再次输出a的时候没有错误


view plaincopy to clipboardprint?
01.A operator = (A &right) //重载的等号  
02.{  
03.    int *rightP = right.getP();  
04.    size = right.getSize();  
05.    p = new int[size];  
06.    memcpy(p, rightP, size*sizeof(int));  
07.    return *this;  
08.} 
A operator = (A &right) //重载的等号
{
 int *rightP = right.getP();
 size = right.getSize();
 p = new int[size];
 memcpy(p, rightP, size*sizeof(int));
 return *this;
} 下边是在参数right和返回值都加了&,这样就没有错误了。


view plaincopy to clipboardprint?
01.A &operator = (A &right)    //重载的等号  
02.{  
03.    int *rightP = right.getP();  
04.    size = right.getSize();  
05.    p = new int[size];  
06.    memcpy(p, rightP, size*sizeof(int));  
07.    return *this;  
08.} 
A &operator = (A &right) //重载的等号
{
 int *rightP = right.getP();
 size = right.getSize();
 p = new int[size];
 memcpy(p, rightP, size*sizeof(int));
 return *this;
} 正好符合我们的分析。

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/xiaobo68688/archive/2010/06/12/5666960.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值