stl中push_back和浅拷贝和深拷贝的问题
来源:互联网 发布:小小dota2数据库 编辑:程序博客网 时间:2024/05/29 14:35
《程序员面试宝典》中stl模板与容器中的一个例子:
#include <iostream>#include <cstdlib>#include <vector>#include <string.h>#include <stdio.h>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;getchar();printf("getchar");return 0;}
上面的这个代码是有问题的,CDemo中的析构函数会重复删除同一片内存区域。但是奇怪的是使用mingw中的g++编译上面的代码时能正常执行,就是说没有出现运行时错误,但是使用visual studio 2008编译时没有出错,运行时出错。
在release模式下运行时vs可能都崩溃,出现下面的错误:
下面分析这块代码错误的原因。主要是由于push_back函数引起的。这个函数会对传递进来的参数进行一次拷贝(调用拷贝构造函数),并将其添加到vector中。如果对象没有拷贝构造函数,编译器会为其生成一个,但是这个编译器生成的拷贝构造函数只是进行了一次浅拷贝,在本例中就是只是复制了str的值,也就是"strend micro"的地址,即拷贝后的对象和原对象的str都是指向同一块内存区域,但是这个拷贝的对象和原对象的析构函数又都会执行,这里就会delete两次。(注意,即使对于一个空类,编译器也会默认生成4个成员函数:默认构造函数,析构函数,拷贝构造函数,赋值函数。)
对析构函数做如下更改可以清晰看出区别:
~CDemo(){static int i=0;cout<<"&CDemo"<<i++<<"="<<(int *)this<<",str="<<(int *)str<<endl;if(str) delete[] str;};
在VS 2008中执行后的结果如下:
用g++编译执行的结果如下:
可以发现无论是vs和g++析构函数都执行了两次,只不过vs有错误提示,g++没有错误提示而已。但是这段代码本质上还是有错误的。顺便打印出的d1的地址,可以发现在getchar之后即main函数退出后析构的是d1。另外一次析构的就是push_back函数调用时创建的那个CDemo对象。
添加拷贝构造函数,并打印出执行的次序,此处添加的拷贝构造函数就是编译器自动生成的拷贝构造函数,只是进行了简单的复制操作,很容易看出问题所在。代码如下:
#include <iostream>#include <cstdlib>#include <vector>#include <string.h>#include <stdio.h>using namespace std;class CDemo{public:CDemo():str(NULL){cout<<"construct invoke!"<<endl;};CDemo(const CDemo &cd){cout<<"copy construct invoke!"<<endl;cout<<"address of copy:"<<(int *)this<<endl;this->str=cd.str;}~CDemo(){static int i=0;cout<<"deconstruct &CDemo"<<i++<<"="<<(int *)this<<",str="<<(int *)str<<endl;if(str) delete[] str;};char * str;};int main(){CDemo d1;cout<<"address of d1: "<<&d1<<endl;d1.str=new char[32];strcpy(d1.str,"trend micro");vector<CDemo> * a1=new vector<CDemo>;a1->push_back(d1);delete a1;return 0;}上面的代码执行后输出如下:
可以看出首先调用了构造函数,并且这个对象的地址位于0x28fef8,然后拷贝构造函数调用,拷贝后的对象的地址位于0x332370。然后析构时首先析构位于0x332370的对象,即拷贝的对象,然后析构位于0x28fef8的对象,即d1。到这里可以很明显看出问题所在了,即位于0x332390的内存区被delete了两次。
注意在delete [] str;后添加str=NULL;这行代码也没有用,因为修改d1对象的str为NULL并不会对拷贝后的对象的str产生影响,拷贝对象的析构函数执行时依然会再次delete这块内存。
修改方法是使用深拷贝:
CDemo(const CDemo &cd){cout<<"copy construct invoke!"<<endl;cout<<"address of copy:"<<(int *)this<<endl;this->str=new char[strlen(cd.str)+1];strcpy(str,cd.str);}注意strcpy也拷贝了字符串结束符'\0'。
上面的问题解决了,还有就是push_back插入元素时的一些构造和拷贝构造的问题。网上有一篇很好的文章
STL 的 vector push_back 类对象时出现问题,偶尔发现 vector 在 push_back 时的调用类对象的拷贝构造函数和析构函数有点特别,简单做下分析。
#include <iostream>#include <vector> using namespace std; struct sss{public: explicit sss(int val) : value(val) { cout << "---init sss " << this << ", value:" << value << endl; } sss(const sss& org) { cout << "---copy " << &org << " to " << this << endl; value = org.value; } ~sss() { cout << "---destory sss " << this << ", value:" << value << endl; } int value;}; int main(int argc, char ** argv){ sss s_tmp(11); int i = 0; vector<sss> vvv; for (i = 0; i < 5; i++) { s_tmp.value++; vvv.push_back(s_tmp); cout << "size: " << vvv.size() << ", capacity: " << vvv.capacity() << endl; } return 0;}
g++中编译的结果如下图:
在visual stdio 2008中执行的结果如下:
结果分析:
vector 每次调用 push_back 时都会拷贝一个新的参数指定的 sss 类对象,这会调用 sss 的拷贝构造函数,第一次的 copy 正常,而且 vector 的实际容量也由 0 变为 1。
第二次调用 push_back,通过输出会发现调用了两次拷贝构造函数,一次析构函数,原来 vector 此时判断容量不够,将容量扩大为原来的两倍,变为 2,并将原来的元素再次拷贝一份存放到新的内存空间,然后拷贝新加的类对象,最后再释放原来的元素。
第三次调用 push_back 时,vector 自动扩大为4,因此拷贝构造函数调用了3次,析构函数调用了2次,程序最终退出了时就析构了 5 次加本身的 sss 类对象一共 6 次。
参考:
由此看来,vector 的 push_back 在发现空间不足时在gcc中自动将空间以 2 的指数增长:0 -> 1 -> 2 -> 4 -> 8 -> 16 -> 32 …
在Visual Studio 2008 中发现 vector 的实际空间增加顺序为:1 - 2 - 3 - 4 - 6 - 9 - 13 - 19 - 28 - 42 - 63 - 94 - 141 - 211 …
查找资料后得知,如此设计的主要目的是为了尽可能的减小时间复杂度;如果每次都按实际的大小来增加 vector 的空间,会造成时间复杂度很高,降低 push_back 的速度。
另外关于 push_back 为什么会执行拷贝构造函数,push_back 的原型为:
void push_back(const _Ty& _Val)
参数是以引用方式传递,按说不会拷贝,但 push_back 实际实现中判断空间不足时是调用 insert 函数添加元素:
void push_back(const _Ty& _Val)
{
// insert element at end
if (size() < capacity())
#if _HAS_ITERATOR_DEBUGGING
{
// room at end, construct it there
_Orphan_range(_Mylast, _Mylast);
_Mylast = _Ufill(_Mylast, 1, _Val);
}
#else /* _HAS_ITERATOR_DEBUGGING */
_Mylast = _Ufill(_Mylast, 1, _Val);
#endif /* _HAS_ITERATOR_DEBUGGING */
else
insert(end(), _Val);
}
- stl中push_back和浅拷贝和深拷贝的问题
- 浅拷贝和深拷贝,以及push_back()的奥秘【转】
- 浅拷贝和深拷贝,以及push_back()的奥秘【转】
- Python中字典的浅拷贝和深拷贝问题
- c++中 拷贝构造函数的深拷贝和浅拷贝--“浅拷贝”与“深拷贝”
- C++中浅拷贝和深拷贝问题
- 讨论深拷贝和浅拷贝问题?
- 深拷贝和浅拷贝使用问题
- 深拷贝和浅拷贝问题
- C++中深拷贝和浅拷贝
- OC中浅拷贝和深拷贝
- java中深拷贝和浅拷贝
- js中深拷贝和浅拷贝
- Java中浅拷贝和深拷贝
- java中深拷贝和浅拷贝
- C#中深拷贝和浅拷贝的例子
- C++中浅拷贝和深拷贝的区别
- C++中浅拷贝和深拷贝的差别
- 列出所有的配置 Repo 源
- JavaWeb的各种中文乱码终极解决方法
- HDU 1.1.4解题报告
- USACO--1.3Mixing Milk
- 1192 回文字符串
- stl中push_back和浅拷贝和深拷贝的问题
- 做个有计划的人
- 【hibernate框架】缓存机制之一级缓存
- C Primer Plus 练习 7-6
- 欢迎使用CSDN-markdown编辑器
- 【很早之前的作品】2DRPG小游戏
- redis源代码分析系列文章
- Online Object Tracking
- Errors running builder 'Android Resource Manager' on Project java.lang.NullPointerException 解决方法