11

例子

struct MyObject {
  MyObject(int value):value(value) { }
  MyObject(MyObject const&o):value(o.value) { }

  int value;
};

假设复制构造函数除了有用之外还做了一些事情。然后

std::function<void()> f() {
  MyObject o;
  std::vector<int> v;
  return [=]() { /* use v and o */ &o; &v; }
}

vo首先复制到初始 lambda 对象中,这很好。但是每次需要移动 lambda 对象时,它们都会再次被复制。虽然v 可以移动,但不是。这是因为 lambda 没有隐式移动构造函数,因为o没有移动构造函数或普通复制构造函数。

有人可以解释这背后的理由吗?

4

2 回答 2

6

我似乎记得这是两个极端之间的折衷,一种是根本不希望隐式生成移动构造函数,另一种是希望在大多数情况下自动生成移动构造函数。

在不希望隐式生成移动构造函数的人中,Dave Abrahams 写了一篇名为Implicit Move Must Go的文章。理由是在某些情况下,即使成员是可移动的,移动构造函数的隐式生成也会破坏不变量。

今年年初(2011 年),委员会决定保留隐式生成移动构造函数,该决定似乎强调了现有代码中的性能提升而不是安全问题(1),Dave 再次发表了关于它的博客。它没有谈论决定的细节,利弊,但对结果也不太满意。

编辑(来自 Jerry Coffin):这是移动构造函数的隐式声明的条件列表:

If the definition of a class X does not explicitly declare a move constructor, 
one will be implicitly declared as defaulted if and only if
— X does not have a user-declared copy constructor,
— X does not have a user-declared copy assignment operator,
— X does not have a user-declared move assignment operator,
— X does not have a user-declared destructor, and
— the move constructor would not be implicitly defined as deleted.

基本思想是,在类中包含任何这些都表明隐式生成的移动 ctor 可能行为不端。虽然这是真的,但列表中的条件对于确定来说既不是必要条件也不是充分条件,因此没有生成许多本来有用的移动 ctor,并且可以生成许多会导致问题的移动 ctor。更糟糕的是,这些规则已经足够长和复杂,很少有人记得它们,而修复它们可能至少会加倍。
[杰瑞的贡献/咆哮结束]

(1)感谢 Gene Bushuyev 洞察为何做出该决定

于 2011-11-29T23:21:31.887 回答
1

有点猜测,但我怀疑这可能与例外有关。也就是说,移动构造函数实际上应该是 noexcept,但是让移动构造函数调用复制构造函数可能会抛出异常。

(试图从这里刷新我的记忆,我认为这涵盖了这个问题)

编辑添加

而我的猜测是错误的。据我所知,正确答案来自这里。复制构造函数的存在表明该类具有不变量,并且默认生成的移动构造函数可能不尊重这些不变量,因此不应生成。

于 2011-11-29T22:59:36.657 回答