vector :: push_back坚持使用复制构造函数,但提供了移动构造函数

Bre*_*kDS 17 c++ gcc move-constructor c++11

我从gcc收到一个奇怪的错误,无法弄清楚原因.我制作了以下示例代码,以使问题更加清晰.基本上,有一个类定义,我为其复制构造函数和复制赋值运算符私有,以防止意外调用它们.

#include <vector>
#include <cstdio>
using std::vector;

class branch 
{
public:
  int th;

private:
  branch( const branch& other );
  const branch& operator=( const branch& other );

public:

  branch() : th(0) {}

  branch( branch&& other )
  {
    printf( "called! other.th=%d\n", other.th );
  }

  const branch& operator=( branch&& other )
  {
    printf( "called! other.th=%d\n", other.th );
    return (*this);
  }

};



int main()
{
  vector<branch> v;
  branch a;
  v.push_back( std::move(a) );

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我希望这段代码能够编译,但是gcc失败了.实际上gcc抱怨"branch :: branch(const branch&)是私有的",据我所知,不应该调用它.

赋值运算符可以工作,因为如果我用main替​​换main()的主体

branch a;
branch b;
b = a;
Run Code Online (Sandbox Code Playgroud)

它将按预期编译和运行.

这是gcc的正确行为吗?如果是这样,上面的代码出了什么问题?任何建议对我都有帮助.谢谢!

maw*_*cks 18

尝试在移动构造函数的声明中添加"noexcept".

我不能引用标准,但最近版本的gcc似乎要求复制构造函数是公共的,或者移动构造函数被声明为"noexcept".无论"noexcept"限定符如何,如果将复制构造函数设置为public,它将在运行时按预期运行.

  • 如果他将复制构造函数公开,那么该对象将被复制,这显然是他试图避免的.无论如何,在这里制作移动构造函数"noexcept"是正确的解决方案,所以+1. (3认同)

Com*_*sMS 10

与前一个答案所建议的不同,gcc 4.7 拒绝此代码是错误的,这个错误已在gcc 4.8中得到纠正.

符合标准的完整行为vector<T>::push_back是:

  • 如果只有一个复制构造函数而没有移动构造函数,push_back则会复制其参数并给出强大的异常安全保证.也就是说,如果push_back由于由向量存储的重新分配触发的异常而失败,则原始向量将保持不变并且可用.这是来自C++ 98的已知行为,也是随之而来的混乱的原因.
  • 如果有一个noexcept用于移动构造函数T,push_back移动从它的参数,并给强异常保证.这里没有惊喜.
  • 如果有一个不是 的移动构造函数,noexcept并且还有一个复制构造函数,push_back则会复制该对象并给出强大的异常安全保证.乍一看这是意想不到的.虽然push_back可以搬到这里,但这只能牺牲强大的例外保证.如果您将代码从C++ 98移植到C++ 11并且您的类型是可移动的,那么这将无声地更改现有push_back调用的行为.为了避免这种陷阱并保持与C++ 98代码的兼容性,C++ 11回归到较慢的副本.这就是gcc 4.7行为的全部内容.但还有更多......
  • 如果有一个移动构造函数,noexcept但根本没有复制构造函数 - 也就是说,元素只能移动而不能复制 - push_back将执行移动但不会提供强大的异常安全保证.这是gcc 4.7出错的地方.在C++ 98中,没有push_back可移动但不可复制的类型.因此,牺牲强大的异常安全性并不会破坏现有代码.这就是允许的原因,原始代码实际上是合法的C++ 11.

cppreference.compush_back:

如果抛出异常,则此函数无效(强异常保证).

如果T的移动构造函数不是noexcept且复制构造函数不可访问,则vector将使用throw move构造函数.如果它抛出,则免除保证并且未指定效果.

或者从C++ 11标准中得到更复杂的§23.3.6.5(我强调的是):

如果新大小大于旧容量,则会导致重新分配.如果没有重新分配,插入点之前的所有迭代器和引用仍然有效.如果除了复制构造函数之外抛出异常,移动构造函数,赋值运算符或T的移动赋值运算符,或者通过任何InputIterator操作都没有效果.如果非CopyInsertable T的移动构造函数抛出异常,则不指定效果.

或者,如果你不喜欢阅读,Scott Meyer的Going Native 2013演讲(从0:30:20开始,有趣的部分在0:42:00左右).