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

时间:2012-07-15 00:36:02

标签: c++ gcc c++11 move-constructor

我从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;
}

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

赋值运算符有效,因为如果我用

替换main()的主体
branch a;
branch b;
b = a;

它将按预期编译并运行。

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

2 个答案:

答案 0 :(得分:18)

尝试在移动构造函数的声明中添加“noexcept”。

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

答案 1 :(得分:10)

与上一个答案不同,gcc 4.7 错误拒绝此代码,这个错误已经corrected in gcc 4.8

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

  • 如果只有复制构造函数且没有移动构造函数,push_back将复制其参数并提供强大的异常安全保证。也就是说,如果push_back由于由向量存储的重新分配触发的异常而失败,则原始向量将保持不变并且可用。这是来自C ++ 98的已知行为,也是随之而来的混乱的原因。
  • 如果noexceptT移动构造函数,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。

请参阅push_back上的cppreference.com

  

如果抛出异常,则此功能无效(强大   例外保证)。

     

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

或者来自C ++ 11标准的更复杂的§23.3.6.5(由我强调):

  

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

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