STL-vector

成員變量

typedef T value_type;
typedef value_type* iterator;
iterator start;
iterator finish;
iterator end_of_storage;

vector迭代器類型就是普通指針類型。
內部維護三個指針,start指向內存起始處,finish指向下一個放內存的地址,end_of_storage指向可用內存末尾。函數

vector數據結構

迭代器

vector的迭代器就是普通指針:佈局

    iterator begin() noexcept {
        return start;
    }
    const_iterator begin() const noexcept {
        return start;
    }
    iterator end() noexcept {
        return finish;
    }
    const_iterator end() const noexcept {
        return finish;
    }
    const_iterator cbegin() const noexcept {
        return start;
    }
    const_iterator cend() const noexcept {
        return finish;
    }

構造函數

vector(size_type n, const value_type& value)爲例。性能

vector(size_type n, const value_type& value) {
    start = allocate_and_fill(n, value);
    finish = start + n;
    end_of_storage = finish;
}

iterator allocate_and_fill(size_type n, const value_type& x) {
    iterator result = data_allocator::allocate(n);   //分配n個T類型所需的內存
    uninitialized_fill_n(result, n, x);             //初始化這n個元素
    return result;
}

template <typename ForwardIterator, typename Size, typename T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,
                                            const T& x) {
  ForwardIterator cur = first;
    for ( ; n > 0; --n, ++cur)
        construct(&*cur, x);            //在地址&*cur處構造
    return cur;
}

首先分配n個元素所須要的內存,而後用value初始化這n個元素,最後修改start,finish,end_of_storage三個指針。this

插入操做

insert(const_iterator position, size_type n, const value_type& val)爲例。3d

template <typename T, typename Alloc>
typename vector<T, Alloc>::iterator vector<T, Alloc>::insert(const_iterator position, size_type n, const value_type& val) {
    const size_type elems_before = static_cast<size_type>(position - start);
    iterator pos = start + (position - start);
    if (static_cast<size_type>(end_of_storage - finish) >= n) {  //狀況1:已有的空間能夠容納n個元素
        const size_type elems_after = finish - pos;
        if (n <= elems_after) {  //插入點以後的元素個數大於等於待插入的元素個數
            copy(position, position + (elems_after - n), pos + n); 
            uninitialized_copy(position + (elems_after - n), cend(), finish);
            fill_n(pos, n, val);
        } else {   //插入點以後的元素個數小於待插入的元素個數
            uninitialized_copy(position, cend(), pos + n);
            fill_n(pos, elems_after, val);
            uninitialized_fill_n(finish, n - elems_after, val);
        }
        finish = finish + n;
    } else {  //狀況2:須要從新分配更大的空間
        const size_type old_capacity = size();
        const size_type new_capacity = old_capacity + max(old_capacity, n); //從新分配更大的內存空間
        iterator new_start = data_allocator::allocate(new_capacity);
        iterator new_finish = new_start;
        iterator new_end_of_storage = new_start + new_capacity;
        //todo: move if noexcept
        new_finish = uninitialized_copy(cbegin(), position, new_start); //將原地址處的元素拷貝到新的內存空間
        new_finish = uninitialized_fill_n(new_finish, n, val);
        new_finish = uninitialized_copy(position, cend(), new_finish);
        destroy(start, finish);     //銷燬原來的內存空間
        deallocate();
        start = new_start;
        finish = new_finish;
        end_of_storage = new_end_of_storage;
    }
    return start + elems_before;
}

若是已有的空間能夠容納n個新元素,那麼直接插入,不然,開闢新的內存空間,而後用原來的元素初始化新內存空間處的元素(實際狀況會根據元素的類型是否有noexcept的移動構造函數,決定是拷貝仍是移動),最後回收原來的內存。從這裏可知,vector在擴容後,原來的迭代器都會失效。指針

vector擴容過程當中拷貝或者移動舊元素的次數

考慮下面的例子:code

struct Foo {
    Foo(int a) :i(a) {}
    Foo(Foo &&rhs) {
        i = rhs.i;
        cout << "move constructor i = " << i << endl;
    }
    Foo(const Foo &rhs) {
        i = rhs.i;
        cout << "copy constructor i = " << i << endl;
    }

    int i;
};

int main() {
    Foo f1(1);
    Foo f2(2);
    Foo f3(3);
    vector<Foo> v;      //默認構造函數,沒有分配內存空間
    v.push_back(f1);
    v.push_back(f2);
    v.push_back(f3);
}

運行後的輸出以下:對象

copy constructor i = 1
copy constructor i = 2
copy constructor i = 1
copy constructor i = 3
copy constructor i = 1
copy constructor i = 2

這個例子中Foo雖然有移動構造函數可是並非noexcept,因此擴容過程當中不會調用移動構造函數而是拷貝構造函數,再來看下爲何有這種輸出結果。
首先插入f1,此時vector還沒分配內存空間,因此先分配容納1個元素的內存空間,而後調用拷貝構造函數在第一個位置構造一個新的Foo,因此第一行輸出copy constructor i = 1。此時內存佈局以下:blog

|           f1           |
start                finish(end_of_storage)

接着插入f2,此時只能容納一個元素,因此須要擴容,擴容後能夠容納兩個元素,在第二個位置用拷貝構造函數拷貝初始化元素2,因此第二行輸出copy constructor i = 2,而後將原來內存處第一個元素拷貝到新內存第一個元素處,因此第三行輸出copy constructor i = 1。此時內存佈局以下:

|           f1         ,           f2           |
start                                      finish(end_of_storage)

接着插入f3,原來的空間只能容納兩個元素,因此仍是須要擴容,擴容後能夠容納四個元素。首先將f3拷貝構造到新地址第三個位置,因此第四行輸出copy constructor i = 3,接着將原來的f1, f2拷貝到新的地址處,因此輸出第五行copy constructor i = 1和第六行copy constructor i = 2。此時內存佈局以下:

|           f1         ,           f2           ,         f3          |       備用       |
start                                                             finish      end_of_storage

若是咱們給Foo的移動構造函數加上noexcept,那麼輸出以下:

copy constructor i = 1
copy constructor i = 2
move constructor i = 1
copy constructor i = 3
move constructor i = 1
move constructor i = 2

分析過程和上面同樣只是擴容過程當中的拷貝所有變成了移動。

啓發

儘可能給自定義的類實現noexcept的移動構造函數,這樣的類配合標準庫使用時會得到更好的性能。

刪除元素

erase(const_iterator first, const_iterator last)爲例:

template <typename T, typename Alloc>
typename vector<T, Alloc>::iterator vector<T, Alloc>::erase(const_iterator first, const_iterator last) {
    iterator erase_start = begin() + (first - cbegin());
    iterator result = copy(last, cend(), erase_start);  //將last後面的元素拷貝(或移動)到first處。
    destroy(result, end());  //析構剩下的元素
    finish = result;
    return erase_start;
}

過程以下:
vector erase過程

erase的一個疑問

先看下面的例子:

struct Foo {
    Foo(int a) :i(a) {}
    Foo(const Foo &rhs) {
        i = rhs.i;
    }

    Foo& operator=(const Foo& rhs) {
        cout << "copy assign from " << rhs.i << " to " << i << endl;
        i = rhs.i;
        return *this;
    }

    ~Foo() {
        cout << "destructor i = " << i << endl;
    }

    int i;
};

int main() {
    Foo f1(1);
    Foo f2(2);
    Foo f3(3);
    vector<Foo> v;
    v.reserve(4);
    v.push_back(f1);
    v.push_back(f2);
    v.push_back(f3);

    v.erase(v.begin());
    cout << "end of erase\n";
}

輸出以下:

copy assign from 2 to 1
copy assign from 3 to 2
destructor i = 3
end of erase
destructor i = 2
destructor i = 3
destructor i = 3
destructor i = 2
destructor i = 1

"edn of erase"後面的能夠不看,那是離開main函數時析構f1, f2, f3,和vector中的f1,f2輸出的,前三行是erase第一個元素輸出的信息。咱們erase了第一個元素,可是前三行並無調用第一個元素f1的析構函數,反而是調用了第三個元素f3的析構函數。

實際上根據以前分析的erase過程,不難理解這樣的結果。
erase前:

|           f1         ,           f2           ,         f3          |       備用       |
start                                                             finish      end_of_storage

erase過程:
將第二個元素f2拷貝給第一個元素,將三個元素f3拷貝給第二個元素,最後析構第三個元素f3。
erase後:

|           f2         ,           f3           |         備用          ,       備用       |
start                                       finish                                end_of_storage

啓發

對於某些類咱們必須定義拷貝或者移動賦值運算符,拷貝或者移動賦值運算符具備釋放原對象資源的職責。好比這裏若是咱們沒有定義拷貝賦值運算符而是使用默認的拷貝賦值運算符,而且Foo內有指針成員變量,erase f1後,那麼f1內的指針將得不到正確的釋放。

注意本文展現的代碼並不是真的STL源碼,進行了不少簡化,可是不影響原理的分析。

相關文章
相關標籤/搜索