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()释放内存。
- #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的析构函数时能运行正确,以后的都会出错。因为一个地址只能释放一次。
解决办法是定义自己的拷贝构造函数实现深拷贝:
- #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
为了进一步验证空间重新分配对对象拷贝的影响,看下面的例子:
- #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的大小,避免之后动态扩展,搬运原有数据引起拷贝构造的调用。
转载于:https://www.cnblogs.com/wuchanming/p/3735971.html
STL容器与拷贝构造函数相关推荐
- C++ STL容器vector篇(二) vector容器的构造函数与赋值操作
构造函数 构造函数的调用有四种方式: 默认构造函数(无参构造); 左闭右开区间元素拷贝给容器本身; 构造函数将n个elem拷贝给容器本身; 拷贝构造函数 代码如下: #include <iost ...
- STL源码剖析---STL容器特征总结(含迭代器失效)
Vector 1.内部数据结构:连续存储,例如数组. 2.随机访问每个元素,所需要的时间为常量. 3.在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化 ...
- C++ STL: 容器vector源码分析
文章目录 前言 vector的核心接口 vector push_back实现 vector 的 Allocator vector 的 push_back 总结 前言 vector 是我们C++STL中 ...
- C++知识点37——拷贝构造函数
无论是C++自定义的类还是STL内部的容器类,会显式的定义类的对象在拷贝.赋值和销毁时执行的操作,一个类通过五个成员函数来控制这些操作:拷贝构造函数.拷贝赋值运算符.移动构造函数.移动赋值运算符和析构 ...
- STL容器汇总(二)
一.stack 先进后出 1.构造 stack stk; //stack采用模板类实现, stack对象的默认构造形式 stack(const stack &stk); //拷贝构造函数 2, ...
- c++STL容器的string
STL容器的string String概念 string是一个类, char*是一个指向字符的指针. string不用考虑内存释放和越界. string提供了一系列的字符串操作函数 string的构造 ...
- STL 容器中的元素必须满足的条件
STL中的容器.迭代器.算法都是模板,因此可以操作任何型别.不论是STL预先定义好的或用户自行定义的都可以.然而,由于某些加诸于元素身上的操作行为,某些需求条件也就相应出现了.STL容器元素必须满足以 ...
- STL 容器简介:C++ 容器:顺序性容器、关联式容器和容器适配器
STL标准容器类简介 标准容器类 说明 顺序性容器 vector 从后面快速的插入与删除,直接访问任何元素 deque 从前面或后面快速的插入与删除,直接访问任何元素 list 双链表,从任何地方快速 ...
- 【STL容器讲解—deque】
STL中deque容器讲解 deque容器的实现 相对于vector容器的区别 原因: deque内部工作原理 构造函数 容器操作 插入操作 deque容器的遍历操作 大小操作 插入和删除 数据存取 ...
- c++之STl容器-string
目录 容器的分类 string string的概念 string的初始化 string的遍历 string的一些基本操作 char*类型和string类型互转 字符串的连接 字符串的查找和替换 str ...
最新文章
- Linux 用户篇——用户管理的配置文件
- 企业级控件库之大数据量分页控件 (非原创)
- 微型计算机可以处理的二进制数据长度,可以处理二进制数据长度的是
- java 获取子类实际的类型名_Java 泛型类 以及 泛型类获取子类的具体参数类型 以及 获取子类型具体参数的实例...
- python读取xlsx_Python读取xlsx文件的实现方法
- matlab表达一次函数,[转载]MATLAB数据拟合例子(一次函数、指数函数、双曲线)...
- ubuntu 国内源
- linux sed 笔记
- 三大工艺因素影响百万像素镜头成像质量
- 非科班无实习如何入职腾讯?后台开发岗个人校招学习路线分享!
- 牛逼顿的一生:当智商高到一定程度,情商就不重要了
- 如何使用 MATLAB 绘制小提琴图
- Win10没有蓝牙功能怎么办 win10蓝牙图标不见了怎么办
- [实变函数]3.3 可测集类
- GD32 CAN波特率计算问题
- USACO2013 island travels
- 【H5+ Quick-cocos2dx整合】之iOS 四 协调H5+和Quick-Cocos2dx之间工作
- 进入Sic-Hub的办法
- FAT32文件系统(一)
- 自适应小清新宇航员404页面模板