我探索了msvc 2013 STL实现并发现了std :: vector :: push_back的实现:
void push_back(const value_type& _Val)
{ // insert element at end
if (_Inside(_STD addressof(_Val))) // <-- is this check really necessary?
{ // push back an element
size_type _Idx = _STD addressof(_Val) - this->_Myfirst;
if (this->_Mylast == this->_Myend)
_Reserve(1);
this->_Getal().construct(this->_Mylast,
this->_Myfirst[_Idx]);
++this->_Mylast;
}
else
{ // push back a non-element
if (this->_Mylast == this->_Myend)
_Reserve(1);
this->_Getal().construct(this->_Mylast,
_Val);
++this->_Mylast;
}
}
我有一个问题:是检查
if(_Inside(_STD addressof(_Val)))
真的有必要吗?此条件检查是_Val是否属于此向量。例如,在以下情况下这种情况属实:
std::vector<int> v(1);
v.push_back(v[0]);
相同向量的元素push_back与其他值有什么区别?
答案 0 :(得分:4)
我不知道标准是否规定了额外的检查,但它避免了如果你将一个元素推入向量中就会发生的细微错误。
假设你这样做,如你的例子那样,
std::vector<int> v(1);
v.push_back(v[0]);
在不执行该检查的实现中。现在,如果向量的容量大于1,一切都很好,v[0]
只是在正确的位置复制构造。
但是如果向量必须重新分配会发生什么?在这种情况下,传递给v[0]
的{{1}}引用在重新分配后立即失效,因此push_back
将尝试在向量内部复制一个不再存在的对象。
您发布的实现中的代码通过检查引用是否指向向量内的元素来避免问题,在这种情况下,它会记录其索引。重新分配后,即使引用无效,索引仍然正确,因此可以无风险地执行复制。
答案 1 :(得分:3)
两个代码路径中的代码完全不同:
construct(this->_Mylast, this->_Myfirst[_Idx])
与:相比:
construct(this->_Mylast, _Val)
原因当然是如果向量的容量耗尽,它需要重新分配其存储空间,这会使引用无效。
如果参数_Val
不是向量的一部分,那么它没有相关性,但如果是,那么我们在重新分配后就不能再使用它了。因此,在第一个代码路径中,_Val
是向量的一部分,该值由其向量索引而不是原始函数参数引用。