vector扩容机制的分析
总结下类型转换:
前言
最近被问到关于vector内存管理的一些问题,在暑假实习中遇到了用vector存储大量数据进行运算频繁使用push_back()时函数计算速度不理想的问题,当我改为使用普通的动态数组来代替时速度大大提升,之前并未细细研究其原因,只是猜个大概,不想面腾讯的时候被问到了,直接裂开…
查看源码
void push_back(_Ty&& _Val)
{ // insert by moving into element at end, provide strong guarantee
emplace_back(_STD move(_Val));
}
可以看到当我们去推入一个新元素时实际push_back还是调用emplace_back,它们有什么区别呢?
实际上:emplace_back是C++11后新加的一个代替push_back的函数,因为之前的push_back存在着临时变量申请资源的浪费,保留了push_back这个接口,但实际还是走的emplace_back。
跟进emplace_back
template<class... _Valty>
decltype(auto) emplace_back(_Valty&&... _Val)
{ // insert by perfectly forwarding into element at end, provide strong guarantee
if (_Has_unused_capacity())
{
return (_Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...));
}
_Ty& _Result = *_Emplace_reallocate(this->_Mylast(), _STD forward<_Valty>(_Val)...);
#if _HAS_CXX17
return (_Result);
#else /* ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv */
(void)_Result;
#endif /* _HAS_CXX17 */
}
可以看到在emplace_back中,它首先会判断容器中是否还有未使用的容量(capacity),这里值得注意的是capacity(容量) >= size(元素个数)的,我们先暂且继续跟进如果未超过容量时
如果未超过容量时跟进_Emplace_back_with_unused_capacity
template<class... _Valty>
decltype(auto) _Emplace_back_with_unused_capacity(_Valty&&... _Val)
{ // insert by perfectly forwarding into element at end, provide strong guarantee
// pre: _Has_unused_capacity()
_Alty_traits::construct(this->_Getal(), _Unfancy(this->_Mylast()), _STD forward<_Valty>(_Val)...);
_Orphan_range(this->_Mylast(), this->_Mylast());
_Ty& _Result = *this->_Mylast();
++this->_Mylast();
#if _HAS_CXX17
return (_Result);
#else /* ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv */
(void)_Result;
#endif /* _HAS_CXX17 */
}
可以看到它的函数解释为当我们还有容量时,则在原数据的最后加上我们推入的新数据。那么当我们容量不够时呢,vector会怎么管理我们的数据和安排内存给它呢
如果未超过容量时跟进_Emplace_back_with_unused_capacity
待更新
如果超过容量时跟进_Emplace_reallocate(加了些自己的理解)
template<class... _Valty>
pointer _Emplace_reallocate(const pointer _Whereptr, _Valty&&... _Val)
{ // reallocate and insert by perfectly forwarding _Val at _Whereptr
// pre: !_Has_unused_capacity()
//_Whereptr是一个常量指针,指向最后一个数据的下标
const size_type _Whereoff = static_cast<size_type>(_Whereptr - this->_Myfirst());
_Alty& _Al = this->_Getal();
const size_type _Oldsize = size();
//如果原来的元素个数已经达到了一个极限,则提示已经太长
if (_Oldsize == max_size())
{
_Xlength();
}
const size_type _Newsize = _Oldsize + 1;
//计算新的容量应为多少,即扩容之后申请多大的内存
const size_type _Newcapacity = _Calculate_growth(_Newsize);
//这里暂时不知道其作用是维护什么的,大概是一个新指针指向新的vector,并更新指向收尾的常数指针指向的值
const pointer _Newvec = _Al.allocate(_Newcapacity);
const pointer _Constructed_last = _Newvec + _Whereoff + 1;
pointer _Constructed_first = _Constructed_last;
//这里应该是指向了新的vector将要放新数据的地方,Whereoff前面是老数据的地方
_TRY_BEGIN
_Alty_traits::construct(_Al, _Unfancy(_Newvec + _Whereoff), _STD forward<_Valty>(_Val)...);
_Constructed_first = _Newvec + _Whereoff;
//这里判断了当前数组最后指向的是不是容器最后,可能存在容器未用完也再申请更大容量的情况?
if (_Whereptr == this->_Mylast())
{ // at back, provide strong guarantee
_Umove_if_noexcept(this->_Myfirst(), this->_Mylast(), _Newvec);
}
else
{ // provide basic guarantee
_Umove(this->_Myfirst(), _Whereptr, _Newvec);
_Constructed_first = _Newvec;
_Umove(_Whereptr, this->_Mylast(), _Newvec + _Whereoff + 1);
}
_CATCH_ALL
_Destroy(_Constructed_first, _Constructed_last);
_Al.deallocate(_Newvec, _Newcapacity);
_RERAISE;
_CATCH_END
//三步骤:孤立所有迭代器,放弃旧的数组,获得新的数组
_Change_array(_Newvec, _Newsize, _Newcapacity);
return (this->_Myfirst() + _Whereoff);
}
我们先分析每次扩容的容量是怎样计算的
扩容分析_Calculate_growth
size_type _Calculate_growth(const size_type _Newsize) const
{ // given _Oldcapacity and _Newsize, calculate geometric growth
const size_type _Oldcapacity = capacity();
if (_Oldcapacity > max_size() - _Oldcapacity / 2)
{
return (_Newsize); // geometric growth would overflow
}
const size_type _Geometric = _Oldcapacity + _Oldcapacity / 2;
if (_Geometric < _Newsize)
{
return (_Newsize); // geometric growth would be insufficient
}
return (_Geometric); // geometric growth is sufficient
}
可以看到它会优先判断我们的旧vector是不是已经很长了,当长再有1/2个当前容器容量就到最大值时,我们的新vector一次扩容容量只会+1,而正常情况下每次扩容容量是原容器容量的1.5倍(在GCC编译器中是2倍),而当计算的新的容量<元素个数时,会将元素个数返回,例如原来容量为1,1.5倍是1.5,显然是小于你推入了一个数据长度变成2的size的。
尽管还有一些没完全明白的具体操作步骤,但vector的扩容机制已经很明白了。
注意
该文章仅个人学习使用,欢迎大家一起交流学习