std :: vector :: push_back的成本是成功还是没有效果?

vso*_*tco 13 c++ stl vector c++11

如果我理解正确的话,在复制或移动期间抛出异常的情况下,由于检查异常的成本很高,std::vector::insert不能保证std::vectors 的提交或回滚(std::list由于显而易见的原因,s 确实存在).我最近看到push_backDOES保证在最后成功插入或没有任何反应.

我的问题如下.假设在push_pack向量期间必须调整大小(重新分配发生).在这种情况下,必须通过复制或移动语义将所有元素复制到新容器中.假设不保证移动构造函数noexcept,std::vector那么将使用复制语义.因此,为了保证上述行为push_pack,std::vector必须检查是否成功复制,如果没有,则通过交换回滚初始向量.这是发生了什么,如果是这样,这不是很昂贵吗?或者,由于重新分配很少发生,可以说摊销成本很低?

How*_*ant 21

在C++ 98/03中,我们(显然)没有移动语义,只有复制语义.而在C++ 98/03中,push_back有很强的保证.C++ 11中强烈的动机之一就是不要破坏依赖这种强有力保证的现有代码.

C++ 11规则是:

  1. 如果is_nothrow_move_constructible<T>::value是真的,请移动,否则
  2. 如果is_copy_constructible<T>::value是,则复制,否则
  3. 如果is_move_constructible<T>::value是真的,请移动,否则
  4. 代码格式不正确.

如果我们在1或2的情况下,我们有强有力的保证.如果我们在案例3中,我们只有基本保证.因为在C++ 98/03中,我们总是在案例2中,我们具有向后兼容性.

案例2保持强有力的保证并不昂贵.一个分配新缓冲区,优选地使用诸如第二矢量的RAII设备.复制到其中,并且只有在所有成功的情况下,才*this与RAII设备交换.这是最便宜的做事方式,无论你是否想要强有力的保证,你都可以免费获得.

案例1也很便宜,以保持强有力的保证.我所知道的最好的方法是首先将新元素复制/移动到新分配的中间.如果成功,则从旧缓冲区移动元素并交换.

比你可能想要的更多细节

libc ++使用相同的算法完成所有3个案例.为此,使用了两个工具:

  1. std::move_if_noexcept
  2. vector类似非std 的容器,其中数据是连续的,但可以从分配的缓冲区的开头处以非零偏移量开始.libc ++调用这个东西split_buffer.

假设重新分配的情况(非重新分配的情况很简单),split_buffer构造时引用了这个vector分配器,其容量是vector其两倍,并且其起始位置设置为this->size()(尽管split_buffer仍然是empty()).

然后将新元素复制或移动(取决于push_back我们所讨论的重载)split_buffer.如果失败,则split_buffer析构函数撤消分配.如果它成功了,那么split_buffer现在有了size() == 1,并且在第一个元素之前split_buffer有确切this->size()元素的空间.

接下来的元素被移动/复制以相反的顺序this所述split_buffer. move_if_noexcept用于此目的,其返回类型为上述3种情况所指定的T const&或者T&&完全符合我们的要求.在每次成功的移动/复制中,split_buffer正在做一个push_front.如果成功,则split_buffernow size() == this->size()+1,并且其第一个元素与其分配的缓冲区的开头处的零偏移量.如果任何移动/复制失败,则split_buffer析构函数会破坏其中的任何内容split_buffer并释放缓冲区.

接下来,split_bufferthis交换它们的数据缓冲区.这是一个noexcept操作.

最后是split_buffer析构,破坏其所有元素,并释放其数据缓冲区.

不需要尝试捕获.没有额外的费用.一切都按照C++ 11的规定运行(并在上面进行了总结).