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

8

根据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?

因为误引用cppreference,被踩了。你所引用的保证是针对emplace_back的,(抱歉,更正)后面还有其他限制。 - Arne Vogel
1
@ArneVogel 我在提问时,这个页面已经被更新了。 - Tavian Barnes
1个回答

9
根据C++14标准,只有当您插入的类型本身具有强异常保证时,才能提供强异常保证。
这里:

23.3.6.5 vector modifiers [ 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 Remarks: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid. If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown while inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible::value is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.

看起来cppreference.com是错误的。


2
@TavianBarnes “除了 T 的移动赋值运算符之外”,你的示例在其移动赋值运算符中抛出异常。 - Galik
好的,那么这个句子并不适用。但是其他任何句子也都不适用,所以我猜在这种情况下emplace()的行为没有任何限制。谢谢! - Tavian Barnes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接