之前一直不是很明白为什么当一个类中还有一个要动态分配内存的指针之类的数据成员时一定要声明一个拷贝构造函数和一个赋值操作符。(尽管你在构造函数和析构函数中分别进行了new和delete操作)
今天看了effective C++的11章才有所明白。
原因是看下面介绍:
当你没定义这2个函数时,
那么当你声明2个对象时,比如
tmpclass a("hello");
tmpclass b("world");
这个时候都是完好正确的,a指向"hello/0",b指向"world/0"。
但当你进行如下操作时,即
b=a;时,由于你没有定义赋值操作符,那么C++会自动生成并调用一个缺省的operator =操作符。这个缺省的赋值操作符会执行从a的成员到b成员的逐个成员的赋值操作,对指针来说就是逐位拷贝。
那么赋值后,就会出现2个问题,
1:b曾指向的内存永远不会被删除,永远丢失,这时产生内存泄露的典型例子
2:现在a和b包含的指针都指向了同一个字符串的地址。那么当一个离开他的生存空间时,其析构函数就会删除掉那块内存。那么另一个对象指向的就是非法空间了。
下面这个代码就说明了这一点、
注意,造成这个问题的原因是C++会自动给没有定义赋值操作符的类生成了一个缺省的操作符函数,而这个操作符函数是个位拷贝的进行赋值
#include<iostream>
using namespace std;
class string1 {
public:
string1(const char *value);
~string1();
// 没有拷贝构造函数和operator=
char *data;
};
string1::string1(const char *value)
{
if (value) {
data = new char[strlen(value) + 1];
strcpy(data, value);
}
else {
data = new char[1];
*data = '/0';
}
}
void donothing(string1 localstring) {
cout<<localstring.data<<endl;
}
string1::~string1() { delete [] data; }
int main(){
string1 s = "the truth is out there";
donothing(s);//该函数调用时localstring会自动调用缺省的拷贝构造函数,也是执行的是位拷贝,所以函数结束后,自动调用析构函数,导致s指向的内存被释放了
cout<<s<<endl;
string1 a("hello");
int flag=1;
while(flag){
flag=0;
string1 b("world");
cout<<a.data<<' '<<b.data<<endl;
a=b;
cout<<a.data<<' '<<b.data<<endl;
}
string1 c=a;
cout<<c.data<< " "<<a.data<<endl;
}
解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 m29)去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。
更多更详细可查看effective c++的第11章