STL容器与默认拷贝构造函数,默认赋值函数

默认拷贝构造函数和默认赋值函数这两个函数都采用“按成员拷贝”的默认方式来实现。也就是说:它们会依次调用每个数据成员的默认拷贝函数和默认赋值函数,除非为它们显示定义了新的构造函数和赋值函数;如此递归下去,真到所有成员都是基本类型为止。

但是要知道,基本类型变量(包括指针)的拷贝和赋值实际上是按bit位进行拷贝,因此假如类中有指针成员,这两个默认函数的执行结果将是两个对象中的对应指针成员指向相同的对象,而这在一般情况下关非所期望的,在这种情况下这两个函数有可能出现隐含错误,甚至出现严重错误,含有指针成员的类中,我们一定要显示实现这两个函数。(在实践中,我发现若是你在一个DLL库中动态分配了一个类对象内存,而该对象的内存释放只在库被释放的时候,你才会去释放该块类对象的内存,可库被加载进来的生命周期跟你的主程序是一样的,这种情况下,你也不用自己去实现拷贝构造函数,即使你自己去实现了,也不用在拷贝构造函数中另外new一块改类对象的内存。)

 

拷贝构造函数:只有单个形参,并且该形参时本类对象的引用(经常使用const修饰)的构造函数.
默认拷贝构造函数:
在没有定义拷贝构造函数的时候,编译器将自动生成一个
默认拷贝构造函数将执行的操作:对该类的每个成员执行初始化

(对于基础类型直接赋值,对于其所有对象成员逐个调用其对应的拷贝构造函数,即使有STL对象数据成员也是一样的,因为string,vector之类的STL对象都是有自己的拷贝构造函数 和 =赋值函数的)


特别的:倘若一个类拥有数组成员,默认拷贝构造函数将复制数组(即复制数组中的每一个元素).
自定义拷贝构造函数:
包含对象成员或基础类型成员的类(即不包含指针),无需显示定义拷贝构造函数.使用默认的即可.
包含指针成员的类,倘若必须自定义拷贝构造函数(或者定义未定义的私有拷贝构造函数以放置被外部拷贝). 倘若要在拷贝构造函数时需要做一些操作也应该自定义拷贝构造函数.
禁止调用拷贝构造函数:
将拷贝构造函数显示声明为私有.(如果连友元和成员函数也禁止执行拷贝操作,则声明拷贝构造为私有后不进行定义)

 

=重载:
类的默认赋值函数:
在没有重载赋值函数时,编译器将自动生成一个.
默认赋值函数将执行的操作:对该类的每个成员执行赋值操作(对于基础类型直接赋值,对于其所有对象成员逐个调用其对应的赋值函数.)倘若一个类拥有数组成员,默认赋值函数将对数组中的每一个元素执行赋值操作.
[自定义赋值函数]的条件和拷贝构造函数相同.
[禁止赋值函数]的方法同样和拷贝构造函数相同.

 

赋值和拷贝构造经常一起使用,通常在用到其中一个时,几乎肯定同时会用到另一个

 

 

以下内容是转载他处的,链接:点击打开链接

所有容器提供的都是“value语意”而非“reference语意”。容器内进行元素的安插操作时,内部实施的是拷贝操作,置于容器内。因此STL容器的每一个元素都必须能够拷贝。---<<C++标准程序库>> 侯捷、孟岩译 p144页原文 

以vector为例,往Vector中(实际上所有STL容器都是这样)放元素,Vector会调用元素类的拷贝构造函数生成的副本,当Vector走出生存期时(),会自动调用其中每个元素的析构函数。比如,如果 vector<myclass> a,然后a.push_back(b);push_back其实是调用myclass的拷贝构造函数将参数b拷贝进去的。由于vector是自动管理的,出了a的作用域外,a会自动消失释放内存。之前push_back到a里面的数据b通过调用b.~myclass()释放内存。

  1. #include <vector>    
  2. using namespace std;   
  3.   
  4. class CDemo{   
  5. public:   
  6. CDemo():str(NULL){}   
  7. ~CDemo(){if(str) delete [] str;}   
  8. char *str;   
  9. };   
  10.   
  11. int main()   
  12. {   
  13. CDemo d1;   
  14. d1.str = new char[32];   
  15. strcpy(d1.str, "trend micro");   
  16.   
  17. vector <CDemo> *a1 = new vector <CDemo>();   
  18. a1 -> push_back(d1);   
  19.   
  20. delete a1;   
  21. return 0;   
  22. }   
#include <vector> 
using namespace std; 

class CDemo{ 
public: 
CDemo():str(NULL){} 
~CDemo(){if(str) delete [] str;} 
char *str; 
}; 

int main() 
{ 
CDemo d1; 
d1.str = new char[32]; 
strcpy(d1.str, "trend micro"); 

vector <CDemo> *a1 = new vector <CDemo>(); 
a1 -> push_back(d1); 

delete a1; 
return 0; 
} 

1. vector <CDemo> *a1 = new vector <CDemo>(); a1是new出来的,所以必须要手工delete.这是对a1本身而言,而与a1内存储的数据无关。
2. a1 -> push_back(d1); 这部操作比较复杂,因为你的vector是存储类,而不是类指针。但是push_back的参数是引用,所以不需要创建d1的拷贝(也就不需要调用拷贝构造)作为参数传递给push_back。但是在push_back中,会创建d1的拷贝d1_1(需要调用拷贝构造),d1_1是存储在a1管理的内存中。
3. delete a1; a1中存有d1_1,所以会删除d1_1,自然会调用d1_1的析构函数。
4. 在main中return 0, d1被自动删除,此时调用d1的析构函数。
5. 因为class CDemo没有拷贝构造函数,所以创建拷贝时只是简单的把新对象中每个成员变量的值设置成与原来的对象相等。相当于运行memcpy。这时问题就来了,因为你的一个成员是char *str; 这样d1,d1_1的str都是指向同一个地址。所以只有第一次调用CDemo的析构函数时能运行正确,以后的都会出错。因为一个地址只能释放一次。

解决办法是定义自己的拷贝构造函数实现深拷贝:

  1. #include <iostream>   
  2. #include <fstream>   
  3. #include <vector>   
  4. using namespace std;  
  5.   
  6. ofstream out("test.out");  
  7. class CDemo{  
  8. public:  
  9.     CDemo():str(NULL){  
  10.         out << "constructor is called" << endl;  
  11.     }  
  12.     CDemo(const CDemo &cd)  
  13.     {  
  14.         out << "copy constructor is called" << endl;  
  15.         this->str = new char[strlen(cd.str)+1];  
  16.         strcpy(str, cd.str);  
  17.     }  
  18.   
  19.     ~CDemo(){  
  20.         if(str){  
  21.             out << "destructor is called" << endl;  
  22.             delete[] str;  
  23.         }  
  24.     }  
  25.     char *str;  
  26. };  
  27.   
  28. int main()  
  29. {  
  30.     vector <CDemo> *a1 = new vector <CDemo>();  
  31.     a1 -> reserve(1);  
  32.     out << a1 -> capacity() << endl;  
  33.       
  34.     CDemo d1;  
  35.     d1.str = new char[32];  
  36.     strcpy(d1.str, "trend micro1");  
  37.     out << "/" << endl;  
  38.     a1->push_back(d1);  
  39.     out << "/" << endl;  
  40.     CDemo d2;  
  41.     d2.str = new char[32];  
  42.     strcpy(d2.str, "trend micro2");  
  43.     out << "/" << endl;  
  44.     a1->push_back(d2);  
  45.     out << "/" << endl;  
  46.     delete a1;  
  47.   
  48.     return 0;  
  49. }  
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

ofstream out("test.out");
class CDemo{
public:
	CDemo():str(NULL){
		out << "constructor is called" << endl;
	}
	CDemo(const CDemo &cd)
	{
		out << "copy constructor is called" << endl;
		this->str = new char[strlen(cd.str)+1];
		strcpy(str, cd.str);
	}

	~CDemo(){
		if(str){
			out << "destructor is called" << endl;
			delete[] str;
		}
	}
	char *str;
};

int main()
{
	vector <CDemo> *a1 = new vector <CDemo>();
	a1 -> reserve(1);
	out << a1 -> capacity() << endl;
	
	CDemo d1;
	d1.str = new char[32];
	strcpy(d1.str, "trend micro1");
	out << "/" << endl;
	a1->push_back(d1);
	out << "/" << endl;
	CDemo d2;
	d2.str = new char[32];
	strcpy(d2.str, "trend micro2");
	out << "/" << endl;
	a1->push_back(d2);
	out << "/" << endl;
	delete a1;

	return 0;
}

1
constructor is called
/
copy constructor is called
/
constructor is called
/
copy constructor is called
copy constructor is called         //搬运之前的元素
destructor is called                    //之前的元素被析构
/
destructor is called                    //析构容器中所有对象
destructor is called
destructor is called                    //析构CDemo d1
destructor is called                   //析构CDemo d2

可以看到,再加入第二个对象的时候,拷贝构造函数被调用的两次,这是因为,在第一次push_back时,vector的capacity是1,是正常调用一次拷贝构造;而第二次push_back时,会发现容量不够了,stl会重新分配一个old_size的两倍的空间,除了新push进来的数据会放在这个新空间,调用一次拷贝构造,原来已经有的数据也要搬过来的,就又要调用拷贝构造。如果把a1 -> reserve(1);修改为a1 -> reserve(2);保证第二次时也有足够的空间,那么程序的运行结果为:

2
constructor is called
/
copy constructor is called
/
constructor is called
/
copy constructor is called
/
destructor is called
destructor is called
destructor is called
destructor is called
为了进一步验证空间重新分配对对象拷贝的影响,看下面的例子:

  1. #include <iostream>   
  2. #include <fstream>   
  3. #include <vector>   
  4. using namespace std;  
  5.   
  6. ofstream out("test.out");  
  7. class CDemo{  
  8. public:  
  9.     CDemo():str(NULL){  
  10.         out << "constructor is called" << endl;  
  11.     }  
  12.     CDemo(const CDemo &cd)  
  13.     {  
  14.         out << "copy constructor is called" << endl;  
  15.         this->str = new char[strlen(cd.str)+1];  
  16.         strcpy(str, cd.str);  
  17.     }  
  18.   
  19.     ~CDemo(){  
  20.         if(str){  
  21.             out << "destructor is called" << endl;  
  22.             delete[] str;  
  23.         }  
  24.     }  
  25.     char *str;  
  26. };  
  27.   
  28. int main()  
  29. {  
  30.     vector <CDemo> *a1 = new vector <CDemo>();  
  31.     a1 -> reserve(1);  
  32.       
  33.     for(int i = 1; i < 5; i ++){  
  34.         out << "/" << endl;  
  35.         out << i << endl;  
  36.         CDemo d;  
  37.         d.str = new char[32];  
  38.         strcpy(d.str, "trend micro1" + i);  
  39.         out << "begin to push_back" << endl;  
  40.         out << "the vector capacity is " << a1 -> capacity() << endl;  
  41.         a1->push_back(d);  
  42.     }  
  43.     out << "/" << endl;  
  44.     delete a1;  
  45.   
  46.     return 0;  
  47. }  
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

ofstream out("test.out");
class CDemo{
public:
	CDemo():str(NULL){
		out << "constructor is called" << endl;
	}
	CDemo(const CDemo &cd)
	{
		out << "copy constructor is called" << endl;
		this->str = new char[strlen(cd.str)+1];
		strcpy(str, cd.str);
	}

	~CDemo(){
		if(str){
			out << "destructor is called" << endl;
			delete[] str;
		}
	}
	char *str;
};

int main()
{
	vector <CDemo> *a1 = new vector <CDemo>();
	a1 -> reserve(1);
	
	for(int i = 1; i < 5; i ++){
        out << "/" << endl;
        out << i << endl;
        CDemo d;
		d.str = new char[32];
		strcpy(d.str, "trend micro1" + i);
		out << "begin to push_back" << endl;
		out << "the vector capacity is " << a1 -> capacity() << endl;
		a1->push_back(d);
	}
	out << "/" << endl;
	delete a1;

	return 0;
}

程序的运行结果:

/
1
constructor is called
begin to push_back
the vector capacity is 1
copy constructor is called
destructor is called
/
2
constructor is called
begin to push_back
the vector capacity is 1
copy constructor is called
copy constructor is called          //搬运之前的元素1
destructor is called                     //之前的元素被析构
destructor is called                     //CDemo d临时对象
/
3
constructor is called
begin to push_back
the vector capacity is 2
copy constructor is called
copy constructor is called         //搬运之前的元素1
copy constructor is called         //搬运之前的元素2
destructor is called                    //之前的元素1被析构
destructor is called                    //之前的元素2被析构
destructor is called                    //CDemo d临时对象
/
4
constructor is called
begin to push_back
the vector capacity is 4
copy constructor is called         //不需要搬运,第三次时容量已经变成4
destructor is called                    //CDemo d临时对象
/
destructor is called                    //析构容器中所有对象
destructor is called
destructor is called
destructor is called
可以看到,容量的确是按照两倍的空间递增,并且原来已经有的数据要搬过来,就要调用拷贝构造。所以为了程序的效率,最好一开始就用reserve确定vector的大小,避免之后动态扩展,搬运原有数据引起拷贝构造的调用。

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值