STL容器內存淺析(1)-vector內存分配策略

版權聲明:原創文章,歡迎轉載,但請註明出處,謝謝。
https://blog.csdn.net/qiuguolu1108/article/details/107146184


vector是一個封裝了動態大小數組的順序容器,它能夠存放各種類型的對象。 可以刪除元素、可以插入元素、可以查找元素,做這些工作我們無需管理容器內存。容器內存管理,這種髒活累活全部交由vector管理。瞭解一下vector的內存管理策略,能夠更加充分的利用內存。

1、定義一個用於測試的類

class A
{
public:
    A(int data = 100)
        :data_(data)
    {
        construct_count_++;
        cout<<"constructor : "<<this<<endl;
    }

    A(const A& a)
    {
        copy_construct_count_++;
        cout<<this<<" : copy constructor form : "<<&a<<endl;
    }

    static void dis_construct_count()
    {
        cout<<"construct count: "<<construct_count_<<endl;
    }

    static void dis_copy_construct_count()
    {
        cout<<"copy construct count: "<<copy_construct_count_<<endl;
    }

    void display()
    {
        cout<<data_<<endl;
    }

    ~A()
    {
        cout<<"deconstructor : "<<this<<endl;
    }

private:
    int data_;
    static int construct_count_;
    static int copy_construct_count_;
};

int A::construct_count_ = 0;
int A::copy_construct_count_ = 0;

定義一個類,加上一些測試打印信息,幫助我們測試vector。

2、vector內存分配策略

vector是一個動態數組,它會根據元素的個數,適當的去申請內存。可以簡單的把vector理解爲,其內部有一個void*指針,用於指向在堆上申請的空間。void*指向的空間用於存放vector元素。vector一直維護着void*空間的大小,當void*指向的堆空間沒有空間存放新插入的元素時,它都會去系統申請之前空間大小的兩倍空間,並把之前的元素全部拷貝到新的空間,釋放掉原空間,在新空間中插入新的元素。
在這裏插入圖片描述

3、vector使用示例

通過一個vector示例,說明vector是如何管理內存的。

vector<A> va;

A a,b,c,d,e;

cout<<endl<<"======================"<<endl;
va.push_back(a);

cout<<endl<<"======================"<<endl;
va.push_back(b);

cout<<endl<<"======================"<<endl;
va.push_back(c);
va.push_back(d);

cout<<endl<<"======================"<<endl;
va.push_back(e);

cout<<endl;

A::dis_construct_count();
A::dis_copy_construct_count();

cout<<endl;

運行結果如下圖:
在這裏插入圖片描述

結合運行的結果,分析一下這些結果是怎麼打印出來的。

3.1 生成vector<A> va容器對象

vector<A> va;

這條語句僅生成了va對象,並沒有爲va對象分配堆上空間。可以假設其內部的數據成員A*指向了nullptr。

在這裏插入圖片描述

3.2 構造5個A類型的對象

在這裏插入圖片描述

A a,b,c,d,e;

這個運行結果很好理解,就是通過構造器生成了5個類A對象。

3.3 向vector中推入對象a

cout<<endl<<"======================"<<endl;
va.push_back(a);

在這裏插入圖片描述
在向vector中推入元素之前,vector還沒有分配保存類A對象的空間,再調用push_back()的時候,發現vector空間爲空,則先去堆上申請一個空間,用於存放對象a。再將對象a存入vector的時候,發生了一次拷貝構造。
在這裏插入圖片描述

3.4 向vector中推入對象b

cout<<endl<<"======================"<<endl;
va.push_back(b);

在這裏插入圖片描述
再次向vector推入對象b,push_back()函數發現va容器中沒有空間了,則它會再向系統申請兩倍之前的空間。將之前的元素a拷貝到新空間,並將新元素b插入到a元素之後。最後將原來的空間釋放掉,也就是原來的a對象會被釋放掉。
在這裏插入圖片描述

3.5 向vector中推入對象c、d

    cout<<endl<<"======================"<<endl;
    va.push_back(c);
    va.push_back(d);

在這裏插入圖片描述

向va容器推入c對象,push_back()發現va沒有空間了,現在容器中有兩個元素,按照內存分配策略,這次push_back()內部的函數會向系統申請4個元素的空間,並把之前容器中的元素a、b全部拷貝到新空間,並釋放原空間。之後插入元素c。再次插入d對象時,此時va容器正好有一個元素空間空閒,所以剛好把d對象放下。
在這裏插入圖片描述
在這裏插入圖片描述

3.6 向vector中推入對象e

cout<<endl<<"======================"<<endl;
va.push_back(e);

在這裏插入圖片描述
插入e對象的過程,和上面類似,如下圖:
在這裏插入圖片描述

3.7 對象a、b、c、d、e和va容器的析構

在這裏插入圖片描述
類A對象的析構,直接調用其析構器就可以了。va容器的析構,首先要調用容器元素的析構器,再釋放va所佔用的空間。

3.8 vector會發生元素的大量搬動

vector<A> va;

A a;

for(int i=0;i<1024;i++)
{
    va.push_back(a);
}

A::dis_construct_count();
A::dis_copy_construct_count();

在這裏插入圖片描述
理想的情況下,應該只發生1024次拷貝構造,但這裏卻發生了2047次拷貝,多了近一半的"多餘"拷貝構造。這不是我們想要的,大量的搬動元素,會早造成性能降低。

4、size()和capacity()函數

vector的成員函數size(),表示容器中實際存放元素的個數。capacity()函數表示vector此刻總共可以容納元素的個數。其中capacity()函數的返回值纔是vector實際佔用空間的大小。

vector<A> va;

A a,b,c,d,e;

va.push_back(a);
va.push_back(b);
va.push_back(c);
va.push_back(d);
va.push_back(e);

cout<<"size     = "<<va.size()<<endl;
cout<<"capacity = "<<va.capacity()<<endl;

在這裏插入圖片描述
根據上面的分析,我們可知,現在的va向系統申請了8個可以存放類A的空間,其中va只使用了前5個空間,最後3個空間並沒有使用。

重點capacity()纔是vector佔用的實際空間,size()僅是vector使用空間的個數,極端情況下capacity()是size()的兩倍左右。

vector<int> vi;

for(int i=0;i<1024+1;i++)
{
    vi.push_back(i);
}

cout<<"size     = "<<vi.size()<<endl;
cout<<"capacity = "<<vi.capacity()<<endl;

在這裏插入圖片描述

雖然存放了1025個元素,但vi容器卻佔用着2048個元素,空間有些浪費。

5、總結

本文通過一個示例,介紹了vector內存分配的策略。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章