所有容器提供的都是“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. class CDemo{
  4. public:
  5. CDemo():str(NULL){}
  6. ~CDemo(){if(str) delete [] str;}
  7. char *str;
  8. };
  9. int main()
  10. {
  11. CDemo d1;
  12. d1.str = new char[32];
  13. strcpy(d1.str, "trend micro");
  14. vector <CDemo> *a1 = new vector <CDemo>();
  15. a1 -> push_back(d1);
  16. delete a1;
  17. return 0;
  18. }

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

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

程序的运行结果:

/////////////////////////////////
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的大小,避免之后动态扩展,搬运原有数据引起拷贝构造的调用。

STL容器与拷贝构造函数的更多相关文章

  1. STL——容器(Map & multimap)的拷贝构造与赋值

    1. Map & multimap 的拷贝构造与赋值 map(const map &mp);               //拷贝构造函数 map& operator=(con ...

  2. STL——容器(Set & multiset)的默认构造 & 带参构造 & 对象的拷贝构造与赋值

    1. 默认构造 set<int> setInt;              //一个存放int的set容器. set<float> setFloat;          //一 ...

  3. STL——容器(List)List 的构造函数

    list<T> lstT -- list 对象的默认构造 list 与 vector 一样,同样采用模板类实现,对象的默认构造形式:list<T> lstT  如: 1 #in ...

  4. 不要在公共接口中传递STL容器

    最近的一个项目,是开发一个framework,提供给公司内部不同的产品线使用. 之间遇到的一个问题,就是STL容器的使用, 而结论是不要在公共接口中传递STL容器: 这里说的STL容器,但主要则是指容 ...

  5. 转:STL容器里存放对象还是指针

    一.问题的引出: 容器可以存放对象,可以存放指针,这里要谈的是两者的使用问题.就是什么时候存放对象更好,什么时候存放指针更好? 二.问题的分析过程: 1. 首先说下stl容器的工作方式   对于内建类 ...

  6. STL - 容器共性机制研究

    C++模板是容器的概念. 理论提高:所有容器提供的都是值(value)语意,而非引用(reference)语意.容器执行插入元素的操作时,内部实施拷贝动作.所以STL容器内存储的元素必须能够被拷贝(必 ...

  7. STL容器vector应用注意事项

    [1]提前分配足够空间以免不必要的重新分配和复制代价 关于vector容器重新分配和复制及析构释放的代价,请参见随笔<STL容器之vector>. 应用示例对比代码如下: #include ...

  8. STL 容器的概念

    STL 容器的概念 在实际的开发过程中,数据结构本身的重要性不会逊于操作于数据结构的算法的重要性,当程序中存在着对时间要求很高的部分时,数据结构的选择就显得更加重要. 经典的数据结构数量有限,但是我们 ...

  9. STL容器共性机制和使用场景

    一.STL容器共性机制 STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放 ...

随机推荐

  1. mac os idea的快捷键

    全局搜索:shift+command+f 搜索类:command+o 光标向前向后移动:command+option+(左/右) 删除一行: command+delete

  2. Linux sar命令工具详细介绍

    sar命令工具详细介绍 by:授客 QQ:1033553122 由于篇幅限制,采用网盘分享, 下载地址: sar命令工具详细介绍.pdf

  3. 【Python】Java程序员学习Python(六)— 流程控制、异常处理

    和Java语言一样,Python也有基本的流程控制,简单了解下即可. 一.流程控制的元素 条件 条件就是布尔值或者布尔值的表达式,要么是True要么是False. 代码块 在Python中,代码块不是 ...

  4. solr学习笔记

    目录 前言 linux部署 使用 配置 使用 前言 solr是apach基于Lucene开发的成熟的框架,这里我们学习如何部署.使用.关于集群会在后面继续添加 linux部署 mkdir /usr/l ...

  5. oracle数据泵备份(Expdp命令)

    Oracle备份方式主要分为数据泵导出备份.热备份与冷备份三种,今天首先来实践一下数据泵备份与还原.数据泵导出/导入属于逻辑备份,热备份与冷备份都属于物理备份.oracle10g开始推出了数据泵(ex ...

  6. [EffectiveC++]item32:确定你的public继承模塑出is-a关系

  7. Django ImportError 模块路径正确,且将文件夹设置为Source Root

    问题描述: 在用PyCharm进行Django项目开发时,由于业务需求需要增加一个外键字段,但在引入外键关联的model时,报错ImportError 解决方案: 无奈中,尝试更改导入模块语句的位置, ...

  8. 个人技术博客(1/2)android布局技巧

    (1)weight属性的合理应用 当使用match_parent(fill_parent)时,需要经过计算,否则会出现如下情况 代码: <LinearLayout xmlns:android=& ...

  9. 解决数据库自增ID的问题

    (1)设置主键自增为何不可取这样的话,数据库本身是单点,不可拆库,因为id会重复. (2)依赖数据库自增机制达到全局ID唯一使用如下语句:REPLACE INTO Tickets64 (stub) V ...

  10. IOS和安卓WEB页面,input输入框被软键盘遮挡解决方法

    本来以为这问题就只有ios才有,身边也没有android机测试,网上一搜,貌似有这个问题的还不少.最后把各种解决方法试了一边,貌似没什么用. 最后是这样解决的: setInterval(functio ...