ARTICLE · 1232085
libcxx stl源码分析-容器vector分析(2)
列表初始化构造函数实现 splitbuffer介绍 resize是如何工作的,有什么潜在的坑
列表初始化构造函数
#include<vector>#include<iostream>class A {public:A(int) {std::cout << "A(int)\n";}};intmain(){std::vector<A> a{5};return 0;}
std::vector<A> a{5};std::vector<A, std::allocator<A> > a = std::vector<A, std::allocator<A> >{std::initializer_list<A>{std::vector<A>::value_type(5)}, std::allocator<A>()};
vector(initializer_list<value_type> __il, const allocator_type& __a): __layout_(__a) {__init_with_size(__il.begin(), __il.size());}
template <class _Tp, class _Allocator /* = allocator<_Tp> */>class _LIBCPP_WARN_UNUSED vector {using __base_type = __vector_layout<_Tp, _Allocator>;using __bound_type = typename __base_type::__bound_type;using _SplitBuffer = typename __base_type::_SplitBuffer;public://// Types//using value_type = _Tp;using allocator_type = _Allocator;using __alloc_traits = allocator_traits<_Allocator>;using reference = _Tp&;using const_reference = const _Tp&;using size_type = typename __alloc_traits::size_type;using difference_type = typename __alloc_traits::difference_type;using pointer = typename __alloc_traits::pointer;using const_pointer = typename __alloc_traits::const_pointer;#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR// Users might provide custom allocators, and prior to C++20 we have no existing way to detect whether the allocator's// pointer type is contiguous (though it has to be by the Standard). Using the wrapper type ensures the iterator is// considered contiguous.using iterator = __bounded_iter<pointer>;using const_iterator = __bounded_iter<const_pointer>;#elseusing iterator = __wrap_iter<pointer>;using const_iterator = __wrap_iter<const_pointer>;#endifusing reverse_iterator = std::reverse_iterator<iterator>;using const_reverse_iterator = std::reverse_iterator<const_iterator>;// A vector contains the following members which may be trivially relocatable:// - pointer: may be trivially relocatable, so it's checked// - allocator_type: may be trivially relocatable, so it's checked// vector doesn't contain any self-references, so it's trivially relocatable if its members are.using __trivially_relocatable _LIBCPP_NODEBUG =__conditional_t<__is_trivially_relocatable_v<pointer> && __is_trivially_relocatable_v<allocator_type>,vector,void>;static_assert(__check_valid_allocator<allocator_type>::value, "");static_assert(is_same<typename allocator_type::value_type, value_type>::value,"Allocator::value_type must be same type as value_type");
一个 vector 包含以下可能可平凡重定位(trivially relocatable)的成员:
pointer:可能是可平凡重定位的,所以检查它
allocator_type:可能是可平凡重定位的,所以检查它
vector 不包含任何自引用,所以如果它的成员可平凡重定位,那么它也可平凡重定位。
继续回到vector的构造函数,列表初始化的构造函数最终会调用
__init_with_size(__il.begin(), __il.size());函数,这个函数实现为
template <class _InputIterator>_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __init_with_size(_InputIterator __first, size_type __n) {auto __guard = std::__make_exception_guard(__destroy_vector(*this));if (__n > 0) {__vallocate(__n);__construct_at_end(std::move(__first), __n);}__guard.__complete();}
上述中__vallocate和__construct_at_end实现在libcxx stl源码分析-容器vector分析(1)中已经分析过此处不再赘述。
resize是如何工作的
class A {public:A(int) {std::cout << "A(int)\n";}A() = default;};intmain(){std::vector<A> a{5};a.resize(10);a.resize(2);return 0;}
template <class_Tp, class_Allocator>void vector<_Tp, _Allocator>::resize(size_type __new_size) {size_type __current_size = size();if (__current_size < __new_size) {if (__new_size <= capacity()) {__construct_at_end(__new_size - __current_size);} else {_SplitBuffer __v(__recommend(__new_size), __current_size, __layout_.__alloc());__v.__construct_at_end(__new_size - __current_size);__layout_.__relocate(__v);}} else if (__current_size > __new_size) {this->__destruct_at_end(this->__layout_.__begin_ptr() + __new_size);}}
下面我们来详细讲解下上述代码实现。以
std::vector<A> a{5};为例来进行分析。上述语句最终的vector的内存布局如下

此时的__begin_是分配的内存的首地址,__capacity_ =__begin_ + 1 =__end_.
template <class _Tp, class _Alloc>typename __vector_layout<_Tp, _Alloc>::size_type__vector_layout<_Tp, _Alloc>::__size() const _NOEXCEPT {return static_cast<size_type>(__end_ - __begin_);}
if (__current_size < __new_size) {if (__new_size <= capacity()) {__construct_at_end(__new_size - __current_size);} else {_SplitBuffer __v(__recommend(__new_size), __current_size, __layout_.__alloc());__v.__construct_at_end(__new_size - __current_size);__layout_.__relocate(__v);}}
template <class _Tp, class _Alloc>typename __vector_layout<_Tp, _Alloc>::size_type__vector_layout<_Tp, _Alloc>::__capacity() const _NOEXCEPT {return static_cast<size_type>(__capacity_ - __begin_);}
_SplitBuffer __v(__recommend(__new_size), __current_size, __layout_.__alloc());__v.__construct_at_end(__new_size - __current_size);__layout_.__relocate(__v);
什么是SplitBuffer
|oooooooooooooooooooxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxoooooooooooooooooooooooo|^ ^ ^ ^__front_cap_ __begin_ __end_ __back_cap_
__recommend扩容机制
// Precondition: __new_size > capacity()template <class _Tp, class _Allocator>inline typename vector<_Tp, _Allocator>::size_typevector<_Tp, _Allocator>::__recommend(size_type __new_size) const {const size_type __ms = max_size();if (__new_size > __ms)this->__throw_length_error();const size_type __cap = capacity();if (__cap >= __ms / 2)return __ms;return std::max<size_type>(2 * __cap, __new_size);}
首先 我们来看下max_size函数,这个函数实现如下
[[__nodiscard__]] size_type max_size() const _NOEXCEPT {return std::min<size_type>(__alloc_traits::max_size(__layout_.__alloc()), numeric_limits<difference_type>::max());}
这个函数返回vector在当前分配器下理论上能容纳的最大元素个数。核心是两个上限取最小值:分配器允许的上限,和difference_type能表示的上限。
__alloc_traits::max_size(__layout_.__alloc()) 这是分配器层面的上限,即当前分配器一次能管理的最大元素数。
对 std::allocator<T>,通常是 SIZE_MAX / sizeof(T),因为分配器内部用 size_t 表示字节数,而每个元素占 sizeof(T) 字节。
自定义分配器可以实现自己的 max_size(),比如受限于某个内存池大小。
它回答的是:“分配器最多能给我多少块T大小的内存?”
numeric_limits::max() difference_type是 vector 迭代器差值类型,通常是 ptrdiff_t(64 位平台上为 int64_t,上限 2^63 - 1)。
它回答的是:“两个迭代器相减,结果还能正确表示的最大元素数是多少?”
vector的size()返回size_type(通常size_t,无符号 64 位),但很多内部操作(迭代器减法、std::distance)用有符号的difference_type。如果元素数超过difference_type::max(),两个迭代器相减就会有符号溢出,产生未定义行为。
std::min(分配器上限, difference_type 上限)分配器能分配得出来(否则分配失败);
迭代器差值能表示得出来(否则内部运算溢出)。
所以取较小者作为max_size()。
注意:每个vector的容量有默认上限值,当vector的capacity大于vector容量上限值的一半时,无论resize(capacity + 1) 这个函数 扩容的大小均为vector的默认上限值
继续回到__recommend函数,在这个函数最后一行有如下实现
return std::max<size_type>(2 * __cap, __new_size);这个就是普通场景下vector的扩容机制。当前vector的容量的2倍 和 resize函数中参数的值的最大值即是扩容的容量。
以resize(10)为例,2 * _cap = 2, __new_size = 10, 所以resize(10) 会导致vector扩容,其容量扩到10,也即__recommend返回10.
_SplitBuffer __v(__recommend(__new_size), __current_size, __layout_.__alloc());__v.__construct_at_end(__new_size - __current_size);

__relocate是如何重定位的
template <class _Tp, class _Alloc>void __vector_layout<_Tp, _Alloc>::__relocate(_SplitBuffer& __buffer) {__annotate_delete();__buffer.__relocate(__begin_, __end_, __capacity_);__annotate_new(__size());}
/// Relocates the objects in the range `[__first, __last)` to the front of the buffer, then swaps/// `__first`, `__last`, and `__capacity` with `__begin_`, `__end_`, and `__back_cap_`,/// respectively.////// Precondition: `__front_spare() == __last - __first`./// Exceptions: This function has a strong exception guarantee.void__relocate(pointer& __first, pointer& __last, pointer& __capacity) {auto __size = __last - __first;auto __new_begin = __begin_ - __size;std::__uninitialized_allocator_relocate(__alloc_, std::__to_address(__first), std::__to_address(__last), std::__to_address(__new_begin));__begin_ = __new_begin;__last = __first;__swap_layouts(__first, __last, __capacity);__front_cap_ = __begin_;}
功能:把[__first, __last)范围内的对象重定位到"缓冲区的前端"(即新缓冲区this的可用空间前端),然后把传入的三个指针__first/__last/__capacity与this的__begin_/__end_/__back_cap_交换。
前置条件:__front_spare() == __last - __first。__front_spare()表示this缓冲区中"前端空余槽位数"(即__begin_ - __front_cap_之类)。这个条件保证:目标位置(新缓冲区前端)恰好能容纳要搬过来的元素数量,不会溢出、也不会浪费。
异常保证:强异常保证(strong exception guarantee)——如果重定位过程中抛异常,this对象的状态保持不变(如同调用未发生)。
// __uninitialized_allocator_relocate relocates the objects in [__first, __last) into __result.// Relocation means that the objects in [__first, __last) are placed into __result as-if by move-construct and destroy,// except that the move constructor and destructor may never be called if they are known to be equivalent to a memcpy.//// Preconditions: __result doesn't contain any objects and [__first, __last) contains objects// Postconditions: __result contains the objects from [__first, __last) and// [__first, __last) doesn't contain any objects//// The strong exception guarantee is provided if any of the following are true:// - is_nothrow_move_constructible<_ValueType>// - is_copy_constructible<_ValueType>// - __is_trivially_relocatable_v<_ValueType>template <class _Alloc, class _ContiguousIterator>void __uninitialized_allocator_relocate(_Alloc& __alloc, _ContiguousIterator __first, _ContiguousIterator __last, _ContiguousIterator __result) {static_assert(__libcpp_is_contiguous_iterator<_ContiguousIterator>::value, "");static_assert(__is_cpp17_move_insertable_v<_Alloc>, "The specified type does not meet the requirements of Cpp17MoveInsertable");using __value_type = typename iterator_traits<_ContiguousIterator>::value_type;if _LIBCPP_CONSTEXPR (__allocator_has_trivial_move_construct_v<_Alloc, __value_type> &&__allocator_has_trivial_destroy_v<_Alloc, __value_type> &&__is_trivially_relocatable_v<__value_type>) {if (!__libcpp_is_constant_evaluated()) {// Casting to void* to suppress clang complaining that this is technically UB.__builtin_memcpy(static_cast<void*>(std::__to_address(__result)),std::__to_address(__first),sizeof(__value_type) * (__last - __first));return;}}using __alloc_traits = allocator_traits<_Alloc>;#ifndef _LIBCPP_CXX03_LANGifconstexpr(!_LIBCPP_HAS_EXCEPTIONS || is_nothrow_move_constructible<__value_type>::value){while (__first != __last) {__alloc_traits::construct(__alloc, std::__to_address(__result), std::move(*__first));__alloc_traits::destroy(__alloc, std::__to_address(__first));++__first;++__result;}} else#endif{auto __destruct_first = __result;auto __guard = std::__make_exception_guard(_AllocatorDestroyRangeReverse<_Alloc, _ContiguousIterator>(__alloc, __destruct_first, __result));auto __iter = __first;while (__iter != __last) {allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__result), std::move_if_noexcept(*__iter));++__iter;++__result;}__guard.__complete();std::__allocator_destroy(__alloc, __first, __last);}}
__uninitialized_allocator_relocate 将 [__first, __last) 范围内的对象重定位到 __result。重定位的意思是:[__first, __last) 中的对象被放入 __result,其效果如同(as-if)经过移动构造和销毁, 但例外情况是:如果已知移动构造函数和析构函数等价于一次 memcpy,那么它们可以根本不被调用。
!_LIBCPP_HAS_EXCEPTIONS || is_nothrow_move_constructible<__value_type>::value)如果「库以无异常模式编译」或「__value_type的移动构造保证不抛异常」,就走这个简单循环;否则走下面的带异常 guard 的复杂路径。
allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__result), std::move_if_noexcept(*__iter));template <class_Tp>using __move_if_noexcept_result_t =__conditional_t<!is_nothrow_move_constructible<_Tp>::value && is_copy_constructible<_Tp>::value, const _Tp&, _Tp&&>;template <class_Tp>__move_if_noexcept_result_t<_Tp>move_if_noexcept(_LIBCPP_LIFETIMEBOUND _Tp& __x) _NOEXCEPT {return std::move(__x);}
!is_nothrow_move_constructible<_Tp>::value && is_copy_constructible<_Tp>::value














































































