2

所以,先写代码:

#include <iostream>
#include <utility>

struct X{
    int i;
    void transform(){}
    X() :i(0){std::cout<<"default\n";}
    X(const X& src): i(src.i){std::cout<<"copy\n";}
    X(X&& msrc) :i(msrc.i){msrc.i=0;std::cout<<"move\n";}
};

X getTransform(const X& src){
    X tx(src);
    tx.transform();
    return tx;
}

int main(){

    X x1;// default
    X x2(x1); // copy
    X x3{std::move(X{})}; // default then move
    X x41(getTransform(x2)); // copy in function ,then what?
    X x42(std::move(getTransform(x2))); // copy in funciton, then move
    X x51( (X()) );//default, then move? or copy?
      // extra() for the most vexing problem
    X x52(std::move(X())); //default then move
    std::cout<<&x41<<"\t"<<&x51<<std::endl;
}

然后从 cygwin + gcc 4.8.2 输出,打开 C++11 功能:

default
copy
default
move
copy
copy
move
default
default
move
0x22aa70        0x22aa50

我不太明白的是 x41 和 x51 的线。对于 x41,函数调用返回的临时值应该调用移动构造函数还是副本?x51的同样问题。
第二个问题是,通过查看输出,x41 和 x51 的构造没有调用任何定义的构造函数,但是对象显然是在内存中创建的。这怎么可能?

4

3 回答 3

6

一个未命名的对象自然比匹配&&得更好。 否则移动语义将不起作用。const&

现在,对默认/复制/移动构造函数的调用减少了,然后人们可能会天真地期望,因为有一个特殊规则允许省略副本,而不考虑可观察的行为(否则必须通过优化来保留):

12.8 对象的复制和移动 § 31

当满足某些标准时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。在没有优化的情况下销毁。123 这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):
尽管如此,如果它从函数返回并直接用于初始化相同类型的对象,则该移动将被省略。
在具有类返回类型的函数的 return 语句中,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作。
— 当一个临时类对象没有绑定到一个引用时(12.2) 将被复制/移动到具有相同 cv 非限定类型的类对象中,可以通过将临时对象直接构造到省略的复制/移动的目标中来省略复制/移动操作。
- [... 2 更多异常处理]

所以,通过你的清单:

X x1;// default
// That's right
X x2(x1); // copy
// Dito
X x3{std::move(X{})}; // default then move
// Yes. Sometimes it does not pay to call `std::move`
X x41(getTransform(x2)); // copy in function ,then what?
// Copy in function, copy to output, move-construction to x41.
// RVO applies => no copy to output, and no dtor call for auto variable in function
// Copy ellision applies => no move-construction nor dtor of temporary in main
// So, only one time copy-ctor left
X x42(std::move(getTransform(x2))); // copy in funciton, then move
// `std::`move` is bad again
X x51( (X()) );//default, then move? or copy? // extra() for the most vexing problem
// Copy-elision applies: default+move+dtor of temporary
// will be optimized to just default
X x52(std::move(X())); //default then move
// And again `std::`move` is a pessimization

我认为使用static_cast可能会避免绑定临时,这意味着可以省略移动,但没有这样的运气:1376. 临时到右值引用的 static_cast感谢@dyp 发现这个问题

于 2014-06-11T21:02:23.380 回答
1

我认为这只是简单的返回值优化。你在函数中创建一个副本 on X tx(src);,然后这个局部变量被返回给主变量。语义上为复制,但实际上省略了复制操作。

正如其他人所说,移动也可以省略。

于 2014-06-11T20:50:47.933 回答
1

根据标准§ 12.8 [复制和移动类对象]

31当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。在没有优化的情况下销毁 。124 这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以组合起来消除多个副本)

  • 在具有类返回类型的函数的返回语句中,当表达式是具有与函数返回类型相同的 cvunqualified 类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时,复制/move 操作可以通过将自动对象直接构造到函数的返回值中来省略。

  • 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动 到具有相同 cv-unqualified 类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的复制/移动

因此,在这两种情况下(即,x41分别x51),您都会遇到复制省略优化效果。

于 2014-06-11T21:10:53.227 回答