C++11:移动/复制构造不明确

C++11: Move/Copy construction ambiguity?

本文关键字:不明确 复制 移动 C++11      更新时间:2023-10-16

在C++11中,我们可以定义复制和移动构造函数,但在同一个类上允许两者?如果是,你如何消除它们的用法歧义?例如:

Foo MoveAFoo() {
  Foo f;
  return f;
}

以上是复印件吗?搬家?我怎么知道?

通常都不是由于RVO。

如果不能进行优化,那么这将是一次移动,因为返回的对象超出了范围(并将在之后被销毁)。如果无法移动,则会进行复制。如果不能复制,它就不会编译。

move构造函数的全部意义在于,当要对即将销毁的对象进行复制时,通常不需要进行完整的复制,并且可以将资源从垂死的对象移动到正在创建的对象。

您可以根据要移动/复制的对象将要发生的情况来判断何时调用复制或移动构造函数。它是否即将超出范围并被销毁?如果是,将调用move构造函数。如果不是,则复制构造函数。

当然,这意味着您可能在同一个类中同时拥有move构造函数和copy构造函数。您还可以使用复制分配运算符和移动分配运算符

更新:可能不清楚移动构造函数/赋值运算符与纯复制构造函数/赋值操作符的确切调用时间。如果我理解正确的话,如果一个对象是用xvalue(eXpiring值)初始化的,就会调用move构造函数。标准§3.10.1规定

xvalue("eXpiring"值)也指对象,通常在其生命周期的结束(以便其资源可以移动,因为示例)。xvalue是某些类型的表达式的结果涉及右值引用(8.3.2)。[示例:调用的结果返回类型为右值引用的函数是xvalue--终止示例]

标准§5的开头写道:

[注意:如果表达式是:,则它是一个xvalue

  • 调用的结果函数,无论是隐式还是显式,其返回类型为对对象类型的右值引用
  • 对的右值转换引用对象类型
  • 类成员访问表达式,指定对象所在的非引用类型的非静态数据成员表达式是x值,或者
  • 指向中成员表达式的.*指针其中第一操作数是x值,第二操作数是指向数据成员的指针

一般来说,这条规则的效果是命名右值引用被视为左值和未命名右值对对象的引用被视为xvalue;对的右值引用无论是否命名,函数都被视为左值--尾注]


举个例子,如果NRVO可以实现,它是这样的:

void MoveAFoo(Foo* f) {
    new (f) Foo;
}
Foo myfoo; // pretend this isn't default constructed
MoveAFoo(&myfoo);

如果NRVO不能完成,但Foo是可移动的,那么你的例子有点像这样:

void MoveAFoo(Foo* fparam) {
    Foo f;
    new (fparam) Foo(std::move(f));
}
Foo f; // pretend this isn't being default constructed
MoveAFoo(&f);

如果它不能移动,但可以复制,那么它就像这个

void MoveAFoo(Foo* fparam) {
    Foo f;
    new (fparam) Foo((Foo&)f);
}
Foo f; // pretend this isn't default constructed
MoveAFoo(&f);

要备份@Seth,以下是标准中的相关段落:

§12.8 [class.copy] p32

当满足或将满足省略复制操作的标准时,除非源对象是函数参数并且要复制的对象由左值指定,否则首先执行选择复制构造函数的重载解析,就好像对象由右值指定一样。如果重载解析失败,或者所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是cv限定的),则会再次执行重载解析,将对象视为左值。[注意:无论是否会发生复制省略,都必须执行此两阶段重载解析。如果不执行省略,它将确定要调用的构造函数,并且即使调用被省略,所选构造函数也必须是可访问的。--end Note]

"消除歧义"只是你的老朋友,过载解决方案:

Foo y;
Foo x(y);            // copy
Foo x(std::move(y)); // move

第一个例子中的表达式y是类型为Foo的左值,它绑定到Foo const &(如果有这样的构造函数,也绑定到Foo &);第二个例子中表达式std::move(y)的类型是Foo &&,因此它将与Foo &&结合(如果没有前者,也与Foo const &结合)。

在您的示例中,MoveAFoo()的结果是一个类型为Foo的临时构造函数,因此如果有Foo &&-构造函数可用,它将绑定到该构造函数,否则将绑定到const-copy构造函数。

最后,在返回Foo(按值)的函数中,如果xFoo类型的局部变量,则语句return x;等效于return std::move(x);——这是一个特殊的新规则,可以更容易地使用移动语义。

Foo MoveAFoo() {
  Foo f;
  return f;
}

这是函数MoveAFoo的定义,它返回类型为Foo的对象。在其主体中,当局部Foo f;超出其范围时创建并销毁。

在此代码中:

Foo x = MoveAFoo();

对象Foo f;是在MoveAFoo函数内部创建的,并直接分配到x中,这意味着不调用复制构造函数。

但在这个代码中:

Foo x;
x = MoveAFoo();

MoveAFoo函数内部创建对象Foo f;,然后创建f的副本并将其存储到x中,并销毁原始f