使用缺省的拷贝构造函数带来的危险性

我此前另外一篇文章通过类String看拷贝构造函数,赋值函数的作用和区别

对于更深的拷贝构造函数讨论大家可以参见这篇帖子

C++类对象的复制-拷贝构造函数

通过编写类String的拷贝构造函数和赋值函数介绍了一些拷贝构造数.本文着重介绍拷贝构造函数的作用和重要性。

首先介绍下拷贝构造函数的使用范围即作用:

1) 一个对象以值传递的方式传入函数体;

2) 一个对象以值传递的方式从函数返回;
3) 一个对象需要通过另外一个对象进行初始化;

如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的

  拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象,这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出(Stack Overflow)。除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。


拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源的情况视为浅拷贝。 

1. 不自行定义拷贝构造函数产生的问题:

class CA
{	
	char* p;
public:
	CA(const char* a)
	{
		if(NULL!=a)
		{
			p = new char[strlen(a)+1];
			strcpy(p,a);
		}
		else
		{
			p = new char[1];
			p='\0';
		}
	};

// 	 CA(CA& mself)
// 	{
// 		p=new char[strlen(mself.p)+1];
// 		strcpy(p,mself.p);
// 	}

	void Show()
	{
		printf("成员变量:%s\n",p);
	}
	~CA()
	{
		printf("析构函数\n");
		delete p;
	}
	
}; 


测试函数:
	CA ca("hello");
	CA cb= ca;
	cb.Show();

执行结果:


当我们不使用自定义拷贝构造函数时,在系统自动调用析构函数时,同一片内存区域p会被释放两次,这时候就会出现上图中的释放错误.而我们将代码中注释掉的自定义拷贝构造函数放开,则不会出现这样的错误。


2. 拷贝构造函数和构造函数,以及无名对象。

先声明定义一个类

class Internet  
{  
public:   
	Internet(char *name)  
	{  
		std::cout<<"载入构造函数"<<std::endl;  
		strcpy(Internet::name,name);  
	}  
	Internet(Internet &temp)  
	{  
		std::cout<<"载入COPY构造函数"<<std::endl;  
		strcpy(Internet::name,temp.name);  
		std::cin.get();  
	}  
	~Internet()  
	{  
		std::cout<<"载入析构函数!";  
		std::cin.get();  
	}  
	void Show()
	{
		std::cout<<name<<std::endl;
	}
protected:  
	char name[20];  
}; 
2.1 无名对象

无名对象可以作为实参传递给函数,可以拿来拷贝构造一个新对象,也可以初始化一个引用的声明。

2.1.1

	Internet b = Internet("test");// 初始化对象定义

图2.1

上面执行的是用无名对象拷贝构造一个对象b。按理说C++先调用构造函数Internet(char*);

创建一个无名对象,然后再调用拷贝构造函数Internet(Internet&);(或许是默认的)创建对象b;但是,由于是用无名对象去拷贝创建一个对象,拷贝完后,无名对象就失去了任何作用,对于这种情况,C++特别将其看作为Internet b = "test";效果一样,而且可以省略创建无名对象这一步。

如测试

Internet b = "test";

或者

Internet b("test");

输出的结果都是和上面一样的,因此可以把上面这3个初始化b的方式看成一样的。

2.1.2

Internet& refs = Internet("测试"); // 初始化引用

上面执行的是拿无名对象初始化一个引用。由于是在函数内部,所以无名对象作为局部对象产生在栈空间中,从作用域上看,该引用与无名对象是相同的,它完全等价于Internet refs = "测试";所以这种使用是多余的。


2.1.3

先定义一个使用自定义类型作参数的函数

void fn(Internet s)
{	
}
测试函数

fn(Internet("测试"));
输出结构仍然是图2.1

这次执行的是无名对象作为实参传递给形参s,C++先调用构造函数创建一个无名对象,然后该无名对象初始化了引用形参s对象,由于实参是在主函数中,所以无名对象是在主函数的栈区中创建,函数fn()的形参s引用的是主函数栈空间中的一个对象。它等价于:

Internet s("测试");
fn(s);
如果对象s仅仅是为了充当函数fn()实参的需要,完全可以用第

fn(Internet("测试"));
来代替。


阅读更多
个人分类: c/c++语言
上一篇通过类String看拷贝构造函数,赋值函数的作用和区别
下一篇C/C++语言void及void指针深层探索
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭