拷贝构造函数的必要性

在C++中,有这样一种构造函数:“拷贝构造函数”,这样的函数有什么用呢?

我们先想一下,拷贝构造函数在什么时候会使用:将整个对象实例作为函数参数传递,将整个对象实例作为返回值返回,以及(恕笔者愚昧也不知道还有没有,本文仅仅讨论参数传递所带来的一些问题)。

好,说了半天,还是代码实在一点,我就直接引入一个代码先:

#include <stdio.h>
#include <string.h>

class CMyString{
private:
	char* buf;
public:
	CMyString(char* str)
	{
		buf = new char[strlen(str) + 1];
		strcpy(buf, str);
	}
	void Show()
	{
		printf("%s\n",buf);
	}
	~CMyString()
	{
		delete buf;
	}
};

int main()
{
	CMyString mstr("1234567");
	mstr.Show();
	return 0;
} 
好的,编译运行一下,一切看似没有问题。内存也正常释放了。嗯嗯。风平浪静。但是,这样是危险的。

不知什么时候,你可能写了这样一个函数,将CMyString类型的对象作为函数的参数使用了。这时候问题出现了。以下是加入函数后的代码:

#include <stdio.h>
#include <string.h>

class CMyString{
private:
	char* buf;
public:
	CMyString(char* str)
	{
		buf = new char[strlen(str) + 1];
		strcpy(buf, str);
	}
	void Show()
	{
		printf("%s\n",buf);
	}
	~CMyString()
	{
		delete buf;
	}
};

void ShowMyString(CMyString str)
{
	str.Show();
}

int main()
{
	CMyString mstr("1234567");

	ShowMyString(mstr);

	mstr.Show();
	return 0;
} 

好嘞,运行。突入起来的弹窗打破了平静。挂了。



这是怎么回事?好好地代码,怎么会突然出现这样的问题。有经验的程序*们可能以下就能够看出问题所在,不过有经验的同行们有可能就不会细看小弟的博文了(当然也欢迎各路高手前来指教)。

选择“retry”,程序停在了某个地方:


从代码那些注释上看来(不是在/**/里面的才叫注释,代码本身也是注释,如果这个代码写的好的话。),是从堆中释放内存出了什么岔子。好吧,我承认,C++你又调皮了。我们还是看看问题在哪儿吧。

略去中间一堆调试的过程,原谅笔者太懒,不愿意把自己发现问题个过程分享出来。不过,容我慢慢解释。

问题就在我们调用函数的地方以及我们的析构函数。你可能已经发现了,在将对象作为函数参数传递时(传值),该对象的拷贝构造函数会被调用,以构造一个对象的副本,将新构造的对象作为参数传入函数。当然,在函数结束的时候,该副本也就该入棺材了,因此,析构函数被调用,对象被析构。

问题就来了,在ShowMyString里面,析构函数会被调用以销毁str,也就是使用了delete释放str指向的堆空间。函数返回,这时候一切正常。但是,当我们的main函数结束的时候,再次调用析构函数销毁mstr,也会使用delete,不对?这个对象里面的buf已经被释放过了啊?嗯嗯。因此便出现了刚才的问题。

这也说明了一点,默认的拷贝构造函数在某些时候是不靠谱的,它的拷贝仅仅是一种浅拷贝,仅仅是将对象占用的那段空间拷贝了一下。因此,两次析构delete的buf是同一段堆空间,也就是那段空间被释放了两次,问题就出现了。

那么,如何避免呢?扯了半天,看起来我们已经跑题了,还是回归正题来吧。我们可以向函数传入指针,或者传入引用,以避免这个过程中的拷贝与析构,这样问题也就解决了。当然,你可能比较倔,或者有强迫症,非要整个对象传值进去,那也不是没办法。

加入拷贝构造函数吧。代码如下:

#include <stdio.h>
#include <string.h>

class CMyString{
private:
	char* buf;
public:
	CMyString(char* str)
	{
		buf = new char[strlen(str) + 1];
		strcpy(buf, str);
	}
	void Show()
	{
		printf("%s\n",buf);
	}
	CMyString(CMyString& str)
	{
		buf = new char[strlen(str.buf) + 1];
		strcpy(buf, str.buf);
	}
	~CMyString()
	{
		delete buf;
	}
};

void ShowMyString(CMyString str)
{
	str.Show();
}

int main()
{
	CMyString mstr("1234567");

	ShowMyString(mstr);

	mstr.Show();
	return 0;
} 

运行,问题引刃而解了。

不会再出现那个犯人的错误了。


我们可以总结以下问题:如果对象中存在堆中分配的空间的指针(不仅仅是堆空间,比如打开了一个文件,一个句柄,等等),那么析构的时候delete会引发一些问题,比如上面的问题。传递参数是原因之一,当然也有其它问题,C++博大精深,难以一下说明白。写一个拷贝构造函数就显得比较重要了,有时候还需要重载=运算之类的,笔者也不能很好的说清楚。

这也再次说明了一个问题,拷贝构造函数还是有必要的,以及写C++代码时要多考虑。

谢谢。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值