1. 程式人生 > 其它 >MSVC2019的vector標準庫實現原始碼分析

MSVC2019的vector標準庫實現原始碼分析

好記性不如爛部落格;stl原始碼剖析那本書不想看,沒事(有事懶得做)看看微軟的vector實現。

以vector<int> 為例

template <class _Ty, class _Alloc = allocator<_Ty>>
class vector { // varying size array of values
private:
    template <class>
    friend class _Vb_val;//????
    friend _Tidy_guard<vector>;

    using _Alty        = _Rebind_alloc_t<_Alloc, _Ty>;//會區分是不是預設分配器_Default_allocator_traits或自定義allocator,是預設的話,就是本身allocator<int>,否則。。。模板巢狀太多了。。
    
using _Alty_traits = allocator_traits<_Alty>; public: static_assert(!_ENFORCE_MATCHING_ALLOCATORS || is_same_v<_Ty, typename _Alloc::value_type>, _MISMATCHED_ALLOCATOR_MESSAGE("vector<T, Allocator>", "T")); using value_type = _Ty;//int using allocator_type = _Alloc;//
using pointer = typename _Alty_traits::pointer; using const_pointer = typename _Alty_traits::const_pointer; using reference = _Ty&; using const_reference = const _Ty&; using size_type = typename _Alty_traits::size_type; using difference_type = typename _Alty_traits::difference_type;
private:
//template <class _Alloc> // tests if allocator has simple addressing

  //_INLINE_VAR constexpr bool _Is_simple_alloc_v = is_same_v<typename allocator_traits<_Alloc>::size_type, size_t>&&
  //is_same_v<typename allocator_traits<_Alloc>::difference_type, ptrdiff_t>&&
  //is_same_v<typename allocator_traits<_Alloc>::pointer, typename _Alloc::value_type*>&&
  //is_same_v<typename allocator_traits<_Alloc>::const_pointer, const typename _Alloc::value_type*>;

using _Scary_val = _Vector_val<conditional_t<_Is_simple_alloc_v<_Alty>, _Simple_types<_Ty>,
        _Vec_iter_types<_Ty, size_type, difference_type, pointer, const_pointer, _Ty&, const _Ty&>>>;//是否是simple型別,選擇不同的types,
      只要不是自定義型別,應該都是選擇第一個,其實第二個無非也是用自定義的型別。
public: using iterator = _Vector_iterator<_Scary_val>; using const_iterator = _Vector_const_iterator<_Scary_val>; using reverse_iterator = _STD reverse_iterator<iterator>; using const_reverse_iterator = _STD reverse_iterator<const_iterator>;
  //兩個建構函式同理,區分自定義分配器型別 #define _GET_PROXY_ALLOCATOR(_Alty, _Al) static_cast<_Rebind_alloc_t<_Alty, _Container_proxy>>(_Al) 這種寫法第一次見,引數沒有實際作用,構造一個新物件 _CONSTEXPR20_CONTAINER vector() noexcept(is_nothrow_default_constructible_v
<_Alty>) : _Mypair(_Zero_then_variadic_args_t{}) { _Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal()));//_GET_PROXY_ALLOCATOR(_Alty, _Getal()),構造一個allocator<_Container_proxy>
}
_CONSTEXPR20_CONTAINER explicit vector(const _Alloc& _Al) noexcept : _Mypair(_One_then_variadic_args_t{}, _Al) { _Mypair._Myval2._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal())); }

  _Compressed_pair<_Alty, _Scary_val> _Mypair;//僅有的成員變數,實際繼承第一個模板引數,負責分配資料記憶體,並持有一個_Scary_val的變數,負責管理資料
  ......
  ......
}

可以看到這些都涉及到了大量的模板超程式設計。先看下_Compressed_pair是什麼,有兩個模板引數_Alty,_Scary_val,第一個已經知道是什麼了allocator<int>, 第二個呢?是一個_Vector_val型別。基本看出這個型別才是真正的資料載體以及維護者

class _Vector_val : public _Container_base {// 基類裡有個_Container_proxy* _Myproxy,維護迭代器的一個東西。
public:
    using value_type      = typename _Val_types::value_type;
    using size_type       = typename _Val_types::size_type;
    using difference_type = typename _Val_types::difference_type;
    using pointer         = typename _Val_types::pointer;
    using const_pointer   = typename _Val_types::const_pointer;
    using reference       = value_type&;
    using const_reference = const value_type&;

    _CONSTEXPR20_CONTAINER _Vector_val() noexcept : _Myfirst(), _Mylast(), _Myend() {}

    _CONSTEXPR20_CONTAINER _Vector_val(pointer _First, pointer _Last, pointer _End) noexcept
        : _Myfirst(_First), _Mylast(_Last), _Myend(_End) {}

    _CONSTEXPR20_CONTAINER void _Swap_val(_Vector_val& _Right) noexcept {
        this->_Swap_proxy_and_iterators(_Right);
        _Swap_adl(_Myfirst, _Right._Myfirst);
        _Swap_adl(_Mylast, _Right._Mylast);
        _Swap_adl(_Myend, _Right._Myend);
    }

    _CONSTEXPR20_CONTAINER void _Take_contents(_Vector_val& _Right) noexcept {
        this->_Swap_proxy_and_iterators(_Right);
        _Myfirst = _Right._Myfirst;
        _Mylast  = _Right._Mylast;
        _Myend   = _Right._Myend;

        _Right._Myfirst = nullptr;
        _Right._Mylast  = nullptr;
        _Right._Myend   = nullptr;
    }

    pointer _Myfirst; // pointer to beginning of array 指標維護資料結構
    pointer _Mylast; // pointer to current end of sequence
    pointer _Myend; // pointer to end of array
};

再接著看_Compressed_pair,目前標準容器庫的實現大致都是這樣的模式。實際是繼承第一個模板引數,那麼本身就是一個分配器。並持有一個_Vector_val 的變數。基本所有的東西都齊全了,第一個引數分配資料,第二個模板引數管理資料。

template <class _Ty1, class _Ty2, bool = is_empty_v<_Ty1> && !is_final_v<_Ty1>>
class _Compressed_pair final : private _Ty1 { // store a pair of values, deriving from empty first
public:
    _Ty2 _Myval2;

    using _Mybase = _Ty1; // for visualization

    template <class... _Other2>
    constexpr explicit _Compressed_pair(_Zero_then_variadic_args_t, _Other2&&... _Val2) noexcept(//標籤派發
        conjunction_v<is_nothrow_default_constructible<_Ty1>, is_nothrow_constructible<_Ty2, _Other2...>>)
        : _Ty1(), _Myval2(_STD forward<_Other2>(_Val2)...) {}
    template <class _Other1, class... _Other2>
    constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2) noexcept(
        conjunction_v<is_nothrow_constructible<_Ty1, _Other1>, is_nothrow_constructible<_Ty2, _Other2...>>)
        : _Ty1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}

    constexpr _Ty1& _Get_first() noexcept {
        return *this;//返回分配器,就是自身
    }

    constexpr const _Ty1& _Get_first() const noexcept {
        return *this;
    }
};

現在來看具體的構造過程,在建構函式裡都有Alloc_proxy函式,看它的實現

template <class _Alloc>
    _CONSTEXPR20_CONTAINER void _Alloc_proxy(_Alloc&& _Al) {//注意此時的_Al 是 allocator<container_proxy>,因為重新生成了一個allocator,具體看上面的_GET_PROXY_ALLOCATOR
_Container_proxy* const _New_proxy = _Unfancy(_Al.allocate(1));//分配一個container_proxy的記憶體16個位元組,因為有兩個指標,
_Construct_in_place(*_New_proxy, this);//為剛才分配的記憶體,呼叫建構函式
 _Myproxy = _New_proxy; 
_New_proxy
->_Mycont = this;//互相記錄,container_base 記錄一個proxy,這個proxy記錄一個my_cont(container_base)
}

這個應該是維護迭代器的,目前只是初始化完成。接著看下vector的push_back操作,因為push_back內部操作還是呼叫了emplace_back。區分了左值和右值的引數呼叫。

template <class... _Valty>
    _CONSTEXPR20_CONTAINER decltype(auto) emplace_back(_Valty&&... _Val) {
        // insert by perfectly forwarding into element at end, provide strong guarantee
        auto& _My_data   = _Mypair._Myval2;
        pointer& _Mylast = _My_data._Mylast;
        if (_Mylast != _My_data._Myend) {//last!=end 可以理解為capacity大於size,利用已有的多餘記憶體構建資料。
            return _Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...);
        }
        _Ty& _Result = *_Emplace_reallocate(_Mylast, _STD forward<_Valty>(_Val)...);//新分配記憶體
#if _HAS_CXX17
        return _Result;
#else // ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv
        (void) _Result;
#endif // _HAS_CXX17
    }

vector的記憶體分配方式,大多網上都有講,這裡具體看下采用什麼樣的記憶體分配策略。

template <class... _Valty>
    _CONSTEXPR20_CONTAINER pointer _Emplace_reallocate(const pointer _Whereptr, _Valty&&... _Val) {
        // reallocate and insert by perfectly forwarding _Val at _Whereptr
        _Alty& _Al        = _Getal();
        auto& _My_data    = _Mypair._Myval2;
        pointer& _Myfirst = _My_data._Myfirst;
        pointer& _Mylast  = _My_data._Mylast;

        _STL_INTERNAL_CHECK(_Mylast == _My_data._Myend); // check that we have no unused capacity

        const auto _Whereoff = static_cast<size_type>(_Whereptr - _Myfirst);//whereptr代表放入資料的位置
        const auto _Oldsize  = static_cast<size_type>(_Mylast - _Myfirst);//當前的size大小

        if (_Oldsize == max_size()) {
            _Xlength();//資料太大
        }

        const size_type _Newsize     = _Oldsize + 1;//已有的資料上新加一個
        const size_type _Newcapacity = _Calculate_growth(_Newsize);
//////////////////////////////////////////////////////////////////
        /*核心部分*/

_CONSTEXPR20_CONTAINER size_type _Calculate_growth(const size_type _Newsize) const {
    // given _Oldcapacity and _Newsize, calculate geometric growth
    const size_type _Oldcapacity = capacity();
    const auto _Max = max_size();

    if (_Oldcapacity > _Max - _Oldcapacity / 2) {
      return _Max; // geometric growth would overflow
    }

    const size_type _Geometric = _Oldcapacity + _Oldcapacity / 2;//在舊的大小基礎上加一半,1 , 2, 3 ,4,5, 7增長方式,前幾個每次都會重新分配記憶體,

    if (_Geometric < _Newsize) {
      return _Newsize; // geometric growth would be insufficient
    }

    return _Geometric; // geometric growth is sufficient 返回的大小一定大於等於newsize

}

////////////////////////////////////////////////////////////////////////
    const pointer _Newvec = _Al.allocate(_Newcapacity);
const pointer _Constructed_last = _Newvec + _Whereoff + 1;
        pointer _Constructed_first      = _Constructed_last;

        _TRY_BEGIN
        _Alty_traits::construct(_Al, _Unfancy(_Newvec + _Whereoff), _STD forward<_Valty>(_Val)...);//在新分配的記憶體裡,構建新加入的資料
        _Constructed_first = _Newvec + _Whereoff;

        if (_Whereptr == _Mylast) { // at back, provide strong guarantee 
            _Umove_if_noexcept(_Myfirst, _Mylast, _Newvec);//將舊資料移動到新的記憶體裡。
        } else { // provide basic guarantee
            _Umove(_Myfirst, _Whereptr, _Newvec);
            _Constructed_first = _Newvec;
            _Umove(_Whereptr, _Mylast, _Newvec + _Whereoff + 1);
        }
        _CATCH_ALL
        _Destroy(_Constructed_first, _Constructed_last);//異常捕捉
        _Al.deallocate(_Newvec, _Newcapacity);
        _RERAISE;
        _CATCH_END

        _Change_array(_Newvec, _Newsize, _Newcapacity);//因為容器重分配記憶體,需要重新調整,first,last,end指標,同時讓所有的迭代器失效
        return _Newvec + _Whereoff;
    }

上面是重新分配資料的邏輯,如果capacity較大時,則會在已有的記憶體上構建資料。

 template <class... _Valty>
    _CONSTEXPR20_CONTAINER decltype(auto) _Emplace_back_with_unused_capacity(_Valty&&... _Val) {
        // insert by perfectly forwarding into element at end, provide strong guarantee
        auto& _My_data   = _Mypair._Myval2;
        pointer& _Mylast = _My_data._Mylast;
        _STL_INTERNAL_CHECK(_Mylast != _My_data._Myend); // check that we have unused capacity
        _Alty_traits::construct(_Getal(), _Unfancy(_Mylast), _STD forward<_Valty>(_Val)...);
        _Orphan_range(_Mylast, _Mylast);//使迭代器失效
        _Ty& _Result = *_Mylast;
        ++_Mylast;
#if _HAS_CXX17
        return _Result;
#else // ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv
        (void) _Result;
#endif // _HAS_CXX17
    }

關於迭代器的問題,是由container_proxy管理的,當建立一個迭代器的時候,呼叫如下函式,proxy記錄相關資訊

_CONSTEXPR20_CONTAINER void _Adopt_unlocked(const _Container_base12* _Parent) noexcept {
        if (!_Parent) {
            _Orphan_me_unlocked_v3();
            return;
        }

        _Container_proxy* _Parent_proxy = _Parent->_Myproxy;
        if (_Myproxy != _Parent_proxy) { // change parentage
            if (_Myproxy) { // adopted, remove self from list
                _Orphan_me_unlocked_v3();
            }
            _Mynextiter                 = _Parent_proxy->_Myfirstiter;//每次adopt,proxy記錄的迭代器都會更新為當前,把上一個記錄在_Mynextiter裡,形成一個chain
            _Parent_proxy->_Myfirstiter = this;
            _Myproxy                    = _Parent_proxy;//當一個迭代器的_Myproxy為空時,即為失效,比如push_back後,end()迭代器就會失效
        }
    }

具體可以看原始碼,看看在那些地方呼叫了orphan_range操作。關鍵部分大概就是這樣了,詳細的細節看下原始碼應該問題不大。

template <class _Alloc>
struct _Default_allocator_traits { // traits for std::allocator
    using allocator_type = _Alloc;
    using value_type     = typename _Alloc::value_type;

    using pointer            = value_type*;
    using const_pointer      = const value_type*;
    using void_pointer       = void*;
    using const_void_pointer = const void*;

    using size_type       = size_t;//預設的size_t
    using difference_type = ptrdiff_t;

    using propagate_on_container_copy_assignment = false_type;
    using propagate_on_container_move_assignment = true_type;
    using propagate_on_container_swap            = false_type;
    using is_always_equal                        = true_type;

    template <class _Other>
    using rebind_alloc = allocator<_Other>;

    template <class _Other>
    using rebind_traits = allocator_traits<allocator<_Other>>;
....
}