std :: vector :: emplace()是否真的在抛出移动构造函数/赋值运算符时提供了强大的异常保证?

时间:2017-07-16 04:53:42

标签: c++ c++11 vector libstdc++

According to cppreference.com,std :: vector :: emplace()无条件地提供强大的异常保证:

  

如果抛出异常(例如通过构造函数),容器将保持不变,就好像从未调用过此函数一样(强异常保证)。

然而,GCC 7.1.1在实践中似乎并非如此。以下程序:

#include <iostream>
#include <vector>

struct ugly
{
  int i;

  ugly(int i) : i{i} { }

  ugly(const ugly& other) = default;

  ugly& operator=(ugly&& other) {
    if (other.i == 3) {
      throw other.i;
    }
    i = other.i;
    return *this;
  }

  ugly& operator=(const ugly& other) = default;
};

int main() {
  std::vector<ugly> vec;
  vec.reserve(6);
  vec.emplace_back(0);
  vec.emplace_back(1);
  vec.emplace_back(2);
  vec.emplace_back(4);
  vec.emplace_back(5);

  try {
    vec.emplace(vec.begin() + 3, 3);
  } catch (int i) {
  }

  for (const auto& u : vec) {
    std::cout << u.i << "\n";
  }

  return 0;
}

打印

0
1
2
4
4
5

事实上,我很难看到如果允许复制/移动,emplace()如何可能提供强有力的保证。要在中间放置,我们必须先将一堆元素移开,然后在其位置构造新元素。如果其中任何一个抛出,我们必须将所有其他元素移回原处,但这些移动也会抛出!

那么谁错了,cppreference还是gcc?

1 个答案:

答案 0 :(得分:9)

根据C++14标准,只有在您插入的类型具有强大的异常保证时,才会保留强异常保证。

下面:

  

23.3.6.5 矢量修饰符 [ vector.modifiers ]

iterator insert(const_iterator position, const T& x);
iterator insert(const_iterator position, T&& x);
iterator insert(const_iterator position, size_type n, const T& x);
template <class InputIterator>
iterator insert(const_iterator position, InputIterator first, InputIterator last);
iterator insert(const_iterator position, initializer_list<T>);
template <class... Args> void emplace_back(Args&&... args);
template <class... Args> iterator emplace(const_iterator position, Args&&... args);
void push_back(const T& x);
void push_back(T&& x);
     

1 备注:如果新尺寸大于旧容量,则会导致重新分配。如果没有重新分配,插入点之前的所有迭代器和引用仍然有效。如果除了复制构造函数,移动构造函数,赋值运算符或T的移动赋值运算符或任何InputIterator操作之外引发异常,则没有任何影响。如果在末尾插入单个元素时抛出异常而T是CopyInsertable或is_nothrow_move_constructible :: value为true,则没有效果。否则,如果非CopyInsertable T的移动构造函数抛出异常,则效果未指定。

所以看起来cppreference.com是错误的。