zoukankan      html  css  js  c++  java
  • STL vector 内存释放

       最近在论坛看到一个提问帖子,问题是vector中存储了对象的指针,调用clear后这些指针如何删除?

    [cpp] view plaincopy
     
    1. class Test  
    2. {  
    3. public:  
    4.     Test() {}  
    5.     ~Test() { cout << "Test des" << endl; }  
    6. };  
    7.    
    8. int main()  
    9. {  
    10.     vector<Test*> vec;  
    11.     vec.push_back(new Test());  
    12.     vec.push_back(new Test());  
    13. vec.push_back(new Test());  
    14. //对象如何进行释放,要调用已定义析构函数  
    15.     vec.clear();  
    16.     return 0;  
    17. }  

    同时最近又看到一道面试题:对于STL中的vector调用clear时,内部是如何操作的?若想将其内存释放,该如何操作?

          针对以上两个问题,我们追踪一下STL源码。

    [cpp] view plaincopy
     
    1. // 清除全部元素。注意并未释放空间,以备可能未来还会新加入元素。  
    2.  void clear() { erase(begin(), end()); }  
    3. //调用vector::erase的两迭代器范围版本  
    4.     iterator erase(iterator first, iterator last) {  
    5.         iterator i = copy(last, finish, first);  
    6.   //finish在vector中定义表示目前使用空间的尾,相当于end(),clear调用时last=finish  
    7.         destroy(i, finish); //全局函数,结构的基本函数  
    8.         finish = finish - (last - first);  
    9.         return first;  
    10. }  

    以上关键就是调用了destroy函数。destory函数在 <stl_construct.h>中定义,为了便于分析整个的构造与释放,将construct函数的内容也进行了摘录。这其中要注意的是traits技术。

    // destroy()单指针版本
    template <class T>
    inline void destroy(T* pointer) {
        pointer->~T();    // 唤起 dtor ~T()
    }
    
    
    
    // destroy()两迭代器版本
    //利用 __type_traits<> 求取最适当措施。
    template <class ForwardIterator>
    inline void destroy(ForwardIterator first, ForwardIterator last) {
      __destroy(first, last, value_type(first));
    }
    
    //判断元素的数值型别(value type)有 non-trivial destructor(自定义析构函数)
    template <class ForwardIterator, class T>
    inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
      typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
      __destroy_aux(first, last, trivial_destructor());
    }
    // 如果元素的数值型别(value type)有 non-trivial destructor(自定义析构函数)
    template <class ForwardIterator>
    inline void
    __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
     for ( ; first < last; ++first)    //遍历元素进行析构
        destroy(&*first);                  //!!!!!关键句!!!!!!!!!
    }
    
    //如果元素的数值型别(value type)有trivial destructor
    template <class ForwardIterator> 
    inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}
    //什么都不做,STL是用了一种保守的方式,只有内建的元素类型(int,float等)进行判定trivial destructor的时候才是__true_type其他一切用户自定义类型都是__false_type
    
    
    // destroy()两迭代器版本,针对char*与wchar_t*的特化版本
    inline void destroy(char*, char*) {}
    inline void destroy(wchar_t*, wchar_t*) {}
    
    //仅仅是对placement new 的一层封装
    template <class T1, class T2>
    inline void construct(T1* p, const T2& value) {
      new (p) T1(value);     // placement new; 唤起 ctor T1(value);
    }
    View Code

    看到这里基本对上述的问题已经有答案了。

            由于对象的指针不是内建对象,所以进行遍历析构。

    for ( ; first <last; ++first)    //遍历元素进行析构

    destroy(&*first);                  //!!!!!关键句!!!!!!!!!

    *iterator是元素类型,&*iterator是元素地址,也就是一个指针。之后调用&*iterator->~T();所以可知当vector中所存储的元素为对象的时候,调用clear()操作的时候系统会自动调用析构函数。但是当存储元素是指针的时候,指针指向的对象就没法析构了。因此需要释放指针所指对象的话,需要在clear操作之前调用delete。

            for(i= 0; i < vItem.size();i++)

                 delete vItem[i];

     测试:

    #include<iostream>
    #include<vector>
    using namespace std;
    
    class A{
    private:
        int a_;
    public:
        A(int a) :a_(a){}
        ~A() { cout << "A destrout" << endl; }
    };
    
    int main()
    {
        vector<A> vec;
        A *p1 = new A(1);
        A *p2 = new A(2);
        A *p3 = new A(3);
    
        vec.push_back(*p1);
        vec.push_back(*p2);
        vec.push_back(*p3);
    
        vec.clear();
    
    }

    为什么调用了6次析构函数?(提示,vector的动态增长。) 

       vector的clear成员函数可以清除vector中的元素,使其大小减至0。但它却不能减小vector占用的内存。

    STLvector内存释放:

     vector的clear成员函数可以清除vector中的元素,使其大小减至0。但它却不能减小vector占用的内存。

    [cpp] view plaincopy
     
    1. int main()  
    2. {  
    3.     vector<int> v(1U<<29);  
    4.     cout<<"step into stage one..."<<endl;  
    5.     sleep(30);  
    6.     v.clear();  
    7.     cout<<"step into stage two..."<<endl;  
    8.     sleep(30);  
    9.     return 0;  
    10. }  

      stage one定义了vector对象,含有2^29个int元素,占用2G内存。stage two调用vector对象的clear成员函数,清除其中所有对象。程序运行过程中,使用ps或者top或者其他工具查看内存占用,会发现,程序在stage one确实占用了大约2GB的内存,但在stage two,程序仍然占用着2G内存。事实上,vector的clear成员只负责对其中每一个元素调用其析构函数,将vector的size置零,并不负责释放vector本身占用的内存空间。

      若想释放vector占用的空间,可以使用swap技巧,

    [cpp] view plaincopy
     
    1. int  
    2. main()  
    3. {  
    4.     vector<int> v(1U<<29);  
    5.     cout<<"step into stage one..."<<endl;  
    6.     sleep(30);  
    7.     vector<int>().swap(v);  
    8.     cout<<"step into stage two..."<<endl;  
    9.     sleep(30);  
    10.     return 0;  
    11. }  

      vector()使用vector的默认构造函数建立临时vector对象,再在该临时对象上调用swap成员,swap调用之后对象v占用的空间就等于一个默认构造的对象的大小(寥寥),临时对象就具有原来对象v的大小,而该临时对象随即就会被析构,从而其占用的空间也被释放

      并不是所有的STL容器的clear成员的行为都和vector一样。事实上,其他容器的clear成员都会释放其内存。比如另一个和vector类似的顺序容器deque,

     

      观察上面程序的行为,会发现程序在stage two的内存占用也是寥寥的了。

      另外,值得一提的是vector具有reserve成员,会提前为容器预留一定的空间,以防止后的push_back操作导致频繁地重新分配内存和移动元素。然而,据我观察,reserve并没有向系统申请内存。

      关于C++ STL容器的默认内存管理,需参考allocator。
      关于deque的内存模型及其clear操作,我不甚了了,请参考您使用的标准库的代码。
      若想深入了解STL的实现,侯捷大叔的《STL源码剖析》是不错的入门资料,希望能够拜读。
      C++标准中并未对STL的实现细节做过多规定,因此不同实现的细节和表现可能不同。

      C++很复杂,但也并非无底黑洞。学好C++很难,但这种学习过程也会使你收获颇丰。
      学习任何一种技术,都需要热情、激情和足够的耐心。
      做好一件事,需要激情,同时激情常常来源于做好一件事情的满足感。
      满足感会增强人分享的欲望,分享的欲望也常常会使人具有亲和力和感染力,使人觉得你有激情,反过来更有利于人做好一件事。
      blabla, over~

            转自 http://www.dutor.net/index.php/2011/04/vector-deque-clear-reserve/

    完美删除vector的“内存空洞”

    问题:stl中的vector容器常常造成删除假象,这对于c++程序员来说是极其讨厌的,《effective stl》大师已经将之列为第17条,使用交换技巧来修整过剩容量。
    内存空洞这个名词是网上的学者给出的,我觉得用来描述这个基本现象特别容易提醒自己vector删除的这个陷阱。

    首先给出一段代码:
    35 void testvector()
    36 {
    38     vector v;
    39     v.push_back(1);
    40     v.push_back(2);
    41     cout << "v size = " << v.size() << " v capacity = " << v.capacity()  << endl;
    42     v.erase(v.begin());
    43     cout << "v size = " << v.size() << " v capacity = " << v.capacity()  << endl;
    44     vector<int>(v).swap(v); // 清除v而且最小化它的容量
    45     cout << "v size = " << v.size() << " v capacity = " << v.capacity()  << endl;
    47 }

    结果如下:
    [hfx@didkey1 bin]$ ./test
    v size = 2 v capacity = 2
    v size = 1 v capacity = 2
    v size = 1 v capacity = 1

    分析:
    可以清楚地看到这个问题,在第一次 v.erase(v.begin());的时候,并没有真正释放删除元素的内存,它的容量还是存着。我也简单描画下这个生活中的问题——
    你拿这一个1000升的水去沙漠上旅行,开始是满的,但是,你的旅途让你的水变成了1升,而且路途中,你没有水资源让你再次灌满,那么,你一直将拖着一个1000升
    容量的大水箱,载着1升水在旅行,你是不允许自己这样做的。你只有把这个水箱切了,切成10升或者1升,小点……
    vector也一样,你把水喝了,并不能把水箱也缩小,要把水箱缩小的做法——
    ——swap()交换函数完美释放内存。
    vector(v).swap(v); // 清除v而且最小化它的容量

    注意:
    a. erase()函数,只能删除内容,不能改变容量大小;
    erase成员函数,它删除了itVect迭代器指向的元素,并且返回要被删除的itVect之后的迭代器,迭代器相当于一个智能指针。
    b. clear()函数,只能清空内容,不能改变容量大小
    c. vector容器删除不自动释放内存,那么它存在内存泄露???不是的,vector在析构函数的时候,对内存进行了释放。
    d. 如果要想在删除内容的同时释放内存,那么你可以选择deque容器。
    e. 关于vector:
    vector相当于c++中的数组,数组在初始化的时候也需要给它一个数组空间大小,vector申请的时候将预留一个空间,比如10,在元素超过10的时候,vector自动将大小
    扩大到两倍,并且将元素拷贝过去。

    用法举例:
     vector(v).swap(v);将v的内存空洞清除
     vector().swap(v);清空vec

    参考:http://blog.csdn.net/sicofield/article/details/8769612


    第一种办法使用 clear ,清空元素,但不回收空间

    1.     vecInt.clear();
    2.     j= vecInt.capacity();      //j=512
    3.     i = vecInt.size();         //i=0


    第二种办法使用 erase循环删除,结果同上

    1. vector <int>::iterator iter=vecInt.begin();
    2.     for ( ;iter!=vecInt.end();)
    3.     {
    4.         iter=vecInt.erase(iter);
    5.     }
    6.     j= vecInt.capacity();      //j=512
    7.     i = vecInt.size();         //i=0        

    erase在每次操作时,迭代器指针会整体前移1,就是每次都会“搬”全部数据,所以vector不适合做频繁删除的容器

    第三种办法 最简单的使用swap,清除元素并回收内存

    1.     vector <int>().swap(vecInt);  //清除容器并最小化它的容量,
    2. //   vecInt.swap(vector<int>()) ;     另一种写法
    3.     j= vecInt.capacity();       //j=0  
    4.     i = vecInt.size();          //i=0        

    该语句是由vector <int>(vecInt).swap(vecInt)的变体而来,一下解释引自csdn:

    std::vector<T>(v).swap(v);的作用相当于:    
      {   
      std::vector<T>   temp(v);//1   
      temp.swap(v);//2   
      }   
      第一句产生一个和v内容一模一样的vector,只不过temp的容量是恰好满足其大小的   
      第二句把v和temp交换   
      然后temp就自动解析掉了   
        
      这样写的作用是:把v的容量缩小到最佳值

        vector<int> v;
        v.push_back(1); v.push_back(2); v.push_back(3);
        cout << v.capacity() << endl;
        v.erase(v.begin());
        cout << "v.size()=" << v.size() << " v.capicyt" << v.capacity() << endl;
    
        vector<int>(v).swap(v);
        cout << "v.size()=" << v.size() << " v.capicyt" << v.capacity() << endl;
    
        vector<int>().swap(v);
        cout << "v.size()=" << v.size() << " v.capicyt" << v.capacity() << endl;

    3
    v.size()=2 v.capicyt3
    v.size()=2 v.capicyt2
    v.size()=0 v.capicyt0
    请按任意键继续. . .

        在《effective STL》和其实很多C++文章中都有指明,用clear()无法保证内存回收。但是swap技法可以。具体方法如下所示:
        vector<int> nums;
        nums.push_back(1);nums.push_back(1);nums.push_back(2);nums.push_back(2);
        vector<int>().swap(nums); //或者nums.swap(vector<int>());

        vector<int>().swap(nums); 或者如下所示 加一对大括号都可以,意思一样的:  
        { 
         std::vector<int> tmp =   nums;   
         nums.swap(tmp); 
        }      
        加一对大括号是可以让tmp退出{}的时候自动析构

        swap技法就是通过交换函数swap(),使得vector离开其自身的作用域,从而强制释放vector所占的内存空间。


    该例中执行这句时,capacity收缩到500

    ××××××××××××××××××××××
    不过以上还是调用stl的函数看到的,不知其内部是如何做的。在网上看到其他人的讨论有这样:
    @@而Cygwin中的GCC用的应该是HP STL或从它继承来的SGI STL,对于小内存有一种缓冲池机制,一旦进池的内存就再也不会交还给系统了
    @@swap 不起作用, 因为原因是 allocator.

    更多:http://blog.csdn.net/sicofield/article/details/8769624

  • 相关阅读:
    WIN7每次从关闭屏幕状态恢复都会挂断宽带连接,请问如何解决?
    程序設計学习之路:不走弯路,就是捷径
    Customize Firefox "Close tab" button
    域名常识
    一到十的英文单词,一十二个月份的英文单词,四季的英文单词,第一,第二第三的英文单词
    Dependency Walker
    刪除當前目錄隱藏文件,非隱藏文件,文件夾等好用的批處理。
    使用 Sandcastle Help File Builder 制作 VS.NET 的 HELP 文件
    字符“23.00”转成int型!Input string was not in a correct format.
    VisualStudio 2010 SP1安装时提示计算机环境导致无法安装的解决办法
  • 原文地址:https://www.cnblogs.com/youxin/p/4351024.html
Copyright © 2011-2022 走看看