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>>; .... }