分享

STL容器中 存放指针与对象区别 遍历删除与释放操作

 wuxinit_ 2019-11-14

~~~~我的生活,我的点点滴滴!! 

STL容器中 存放指针与对象区别 遍历删除与释放操作 - 破茧 - CSDN博客

https://blog.csdn.net/AC_huang/article/details/29382629                 

            c++中我相信大家经常要用到STL里面的各种容器来存放自己的数据,既然我们用的这么频繁那么就相应该有一些疑问?

 1.容器里面什么时候应该存指针?
 2.容器里面什么时候应该存对象?
 3.容器怎么在遍历的时候删除某元素?
 4.容器应该怎么释放掉?

 一.分析一下STL里面工作方式
              对于内建类型(int float char等),容器的工作方式是纯粹的位拷贝,这里没有什么需要多说的。
对于自定义的对象,容器容纳了对象(比如通过insert或push_back等),但容器中存放的对象不是你给它们的那个对象,因为两个对象在内存中的位置不一样。此外,当你从容器中获取一个对象时,你所得到的对象不是容器里的那个对象。取而代之的是,当你向容器中添加一个对象(比如通过insert或push_back等),进入容器的是你指定的对象的拷贝。拷进去,拷出来。拷贝是STL的方式。可以通过自己写一个例子打印出地址来看。

二.存放对象的情况
             明白了容器的工作方式,那么进一步来讨论容器存放对象和指针在操作过程中的开销。内建类型的数据进行拷贝的方式是位拷贝,自定义类型的数据进行拷贝会调用类的拷贝构造函数,这个函数是每个类都有的,如果类中没有显式的声明那么编译器也会给提供一个默认的拷贝构造函数。如果一个类的数据非常多,或者包含其他复杂自定义类型,那么此时类的拷贝构造的开销是非常大的。
此时容器中要是存放的是对象vector,那么一个简单的插入操作的代价也是惊人的,更别说什么排序之类的操作,很容易带来性能上的瓶颈,这个时候就需要在容器中存放对象的指针vector,这样的话就避免了这些多余的拷贝消耗,因为指针就是一个机器字长,拷贝的代价可以忽略不计。

  1. typedef std::vector ObjectVector;
  2. typedef std::vector PointerVector;
  3. //下面是存贮对象的情况
  4. begin = GetTickCount();
  5. ObjectVector objectVector;
  6. for (int i = 0; i < MAX; i++)
  7. {
  8. <span style="white-space:pre"> </span>objectVector.push_back(*pCom);
  9. }
  10. end = GetTickCount();
  11. cout << "存放对象消耗的时间:";
  12. cout << end - begin << "毫秒 ";
  13. //下面是存贮指针的情况
  14. begin = GetTickCount();
  15. PointerVector pinterVector;
  16. for (int i = 0; i < MAX; i++)
  17. {
  18. pinterVector.push_back(pCom);
  19. }
  20. end = GetTickCount();
  21. cout << "存放指针消耗的时间:";
  22. cout << end - begin << "毫秒 ";

下面的结果是在Release版本下,并且编译器的优化关闭的情况下,这和我们目前的客户端设置一样:

MAX = 4000


MAX = 40000


MAX = 400000


上面的数据没有用统计学的方法去测试,只是取了一次结果,我测试了多次结果在数量级上是一样的(用上面的数据只是说明拷贝的代价是巨大的,并没有强调必须用指针)。
               分析完了拷贝的性能消耗,再看看另一个问题,就是声明了一个存放基类对象的容器,如果此时向容器中插入子类的对象,那么子类特有的那些内容就会被无情剥离(slicing)。这是一个很严重的问题。解决的方法还是使用基于指针的容器。

三.存放指针的情况

               上面提到的两个问题用指针确实比用对象好,问题不是这么绝对。在上面考虑拷贝消耗的时候有个前提:如果一个类的数据非常多,或者包含其他复杂自定义类型,并且需要大量的使用需要容器内部对象拷贝的操作。如果一个对象中就是几个简单的内建类型,或者干脆就是一个简单的内建类型的数据,那么再用指针可真是得不偿失了,因为使用指针需要程序员去管理内存。完全没有必要为了节省几个int类型的拷贝消耗而去自己去做内存的管理,确实完全没有必要。用指针就需要自己手动的去管理这些指针所指向的内存,stl容器确实可以动态申请内存使自己变大以容纳更多的元素,但这些动态空间存放的是你的指针,而并不是你指针指向的动态内存,你的指针内存当然需要你去管理,如果实在不想做这些管理工作,可以去使用智能指针。

四.总结一下存指针与对象

1.stl容器可以存放内建类型、自定义类型、指针类型的元素。

2.元素如果是内置数据类型,那么就存放数据本身。

3.元素如果是复杂类型,并且在使用容器的过程中需要容器的元素进行大量的拷贝操作的时候,就要考虑在容器中放入指针;

4.存放指针容易出现内存的泄露,所以在使用的时候需要考虑清楚,如能接口设计的合理,能保证容器在使用的过程中不进行大量的拷贝工作,在容器中存放对象是最好的了。

5.使用智能指针是一种两种优点都兼备的,既有指针的操作效率,又避免了自己手动管理内存带来的问题。

6.指针可以解决派生类对象存放在使用基类实例化的容器中的剥离(slicing)问题。
在考虑容器中是存放对象还是指针的时候脑子里时刻要想到,我的操作需要容器做多少拷贝工作,这些拷贝操作带来的损耗能否接受,从这个本质问题上把握好了,选择起来就不是问题了,要根据实际情况灵活运用。

五. STL遍历过程中删除元素

如果想在容器遍历过程中删除里面某个值时,用迭代器(这是最简单的方法),看下面代码:

  1. std::list< int> List;
  2. std::list< int>::iterator itList;
  3. for( itList = List.begin(); itList != List.end(); )
  4. {
  5. if( WillDelete( *itList) )
  6. {
  7. itList = List.erase( itList);
  8. }
  9. else
  10. itList++;
  11. }
  12. 或者:
  13. std::list< int>::iterator itList;
  14. for( itList = List.begin(); itList != List.end(); )
  15. {
  16. if( WillDelete( *itList) )
  17. {
  18. List.erase( itList++);
  19. }
  20. else
  21. itList++;
  22. }

重点是当删除后就要往后移一位,不然下次遍历到那时,已经不存在,就会报错的。

六. STL中删除元素释放问题

               STL中不管是erase或remove都不会释放对象的内存空间,他只是释放了STL里面用来存放这个对象或指针的空间,所以依然需要自己去手动释放,并且释放是有先后顺序的,要先delete 在调用STL里容器对应的erase函数。

  1. list<obj *>m_list;
  2. list<obj *>::iterator ite;
  3. for( ite = m_list.begin(); ite != m_list.end(); ++ite)
  4. {
  5. delete (*ite);
  6. ite = m_list.erase(ite);
  7. }
总之一条原则, 你 new 的, 你去 delete 释放。不是你 new 的, 就不该你去释放。你往 list 里面插入元素时所需的内存是 list 自己申请的, 那它就会自己释放。但是你存在里面的对象不是他的,他不会管!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多