不好驯服的析构函数
本文假定您对C++的面向对象机制有一些了解,并且对指针比较清楚。您可以随便转载,但是必须保证文章的完整性,并且注明出处。
很愿意和您交朋友:xiaobo68688@qq.com。
注:想问我是学C还是C++的朋友就不要发邮件了,谢谢!
首先看一个深拷贝的例子
- #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,遇到了好多关于深拷贝的问题,而且这问题出的很怪异(我会在下午提出),因此想到了拷贝构造函数,所以在这里需要深究一下。
拷贝构造函数的参数是引用类型。
我们可以试想一下不是引用的情况,也就是说,拷贝构造函数是下边这个样子:
- A::A(A right)
- {
- size = right.getSize();
- p = new int[size];
- memcpy(p,right.getP(), sizeof(int)*size);
- }
参数传递的是一个对象,按值传递,那么这个对象就会被复制到right,复制的时候又会调用拷贝构造函数,那又会复制一个对象到第二次调用的这个拷贝构造函数里边,因此又需要第三次调用拷贝构造函数。。。想一下,是不是无限循环?
简单地说:传值需要调用拷贝构造函数,而不是引用类型的拷贝构造函数又需要调用拷贝构造函数。因此无限循环。
Ok,把引用的这个问题说明白,下边说一下我这几天遇到的这个问题:
请先看一段代码:
- #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的时候没有错误
- A operator = (A &right) //重载的等号
- {
- int *rightP = right.getP();
- size = right.getSize();
- p = new int[size];
- memcpy(p, rightP, size*sizeof(int));
- return *this;
- }
下边是在参数right和返回值都加了&,这样就没有错误了。
- A &operator = (A &right) //重载的等号
- {
- int *rightP = right.getP();
- size = right.getSize();
- p = new int[size];
- memcpy(p, rightP, size*sizeof(int));
- return *this;
- }
正好符合我们的分析。