临时 T 作为参数是否应该在 C++11 中调用 T(const T&) 或 T(T&&)?

Should a temporary T, as a parameter, invoke T(const T&) or T(T&&) in C++11?

本文关键字:调用 const 参数 是否 临时 C++11      更新时间:2023-10-16

所以,先写代码:

#include <iostream>
#include <utility>
struct X{
    int i;
    void transform(){}
    X() :i(0){std::cout<<"defaultn";}
    X(const X& src): i(src.i){std::cout<<"copyn";}
    X(X&& msrc) :i(msrc.i){msrc.i=0;std::cout<<"moven";}
};
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,从函数调用返回的临时函数应该调用move构造函数还是调用副本?x51也有同样的问题。
第二个问题是,通过查看输出,x41和x51的构造没有调用任何定义的构造函数,但是对象显然是在它们驻留在内存中时创建的。这怎么可能呢?

一个未命名的对象自然比const&更匹配&&
否则移动语义将无法工作。

现在,对默认/copy/move-构造函数的调用比人们可能天真地期望的要少,因为有一个特殊的规则允许省略副本,而不考虑可观察到的行为(否则必须通过优化来保留):

12.8对象的复制和移动§31

满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象在没有优化的情况下会被销毁的时间的后期这种拷贝/移动操作的省略,称为拷贝省略,在以下情况下是允许的(它们可以组合在一起以消除多个拷贝):
但是,如果它是从函数返回并直接用于初始化相同类型的对象,则该移动将被省略。
-返回语句中的返回语句具有类返回类型的函数,当表达式是与函数返回类型相同的非易失性自动对象(函数或catch子句形参除外)的名称时,可以通过直接将自动对象构造为函数的返回值来省略复制/移动操作。
-当一个没有绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-不限定类型的类对象时,可以通过将临时对象直接构造为省略的复制/移动的目标来省略复制/移动操作。- - - - - -[…

那么,在你的列表中:

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。感谢@dyp发现这个问题

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

31当满足某些条件时,允许实现省略类的复制/移动构造object,即使为复制/移动操作选择了构造函数和/或对象的析构函数有副作用。在这种情况下,实现处理省略的复制/移动的源和目标操作只是引用同一对象的两种不同方式,以及该对象的析构发生在时间的后期,如果没有优化,这两个对象就会被摧毁复制/移动操作的省略,称为复制省略,在以下情况下是允许的可合并以消除多个副本)

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

  • 当一个临时类对象没有被绑定到引用(12.2)时将被复制/移动对于具有相同cv- undefined类型的类对象,可以通过将临时对象直接构造为省略的copy/move

  • 的目标

因此,在这两种情况下(即分别为x41x51),您将体验到复制省略优化效果。

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

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