【问题标题】:Should a temporary T, as a parameter, invoke T(const T&) or T(T&&) in C++11?临时 T 是否应该作为参数调用 C++11 中的 T(const T&) 或 T(T&&)?
【发布时间】:2014-06-11 20:41:08
【问题描述】:

所以,代码优先:

#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 的构造没有调用任何定义的构造函数,但是对象显然是在内存中创建的。这怎么可能?

【问题讨论】:

  • 一个未命名的对象自然比const&amp; 匹配&amp;&amp; 更好。否则移动语义将不起作用。尽管如此,如果它是从函数返回并直接用于初始化相同类型的对象,则该移动将被省略。
  • 有趣。看起来 g++ 版本必须更多地处理他们的移动语义:省略副本比仅仅移动更好。

标签: c++ c++11 move-constructor temporary-objects


【解决方案1】:

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

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

12.8 对象的复制和移动§ 31

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。在没有优化的情况下销毁。123 这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):
不过,如果它从函数返回并直接用于初始化相同类型的对象,则该移动将被省略。
在返回语句中在具有类返回类型的函数中,当表达式是具有相同 cv- 的非易失性自动对象(函数或 catch 子句参数除外)的名称时非限定类型作为函数返回类型,直接在函数返回值中构造自动对象即可省略复制/移动操作。
— 当尚未绑定到引用的临时类对象 (12.2) 将被复制/移动到具有相同 cv-unqualified 类型的类对象时,可以通过以下方式省略复制/移动操作将临时对象直接构造到省略的复制/移动的目标中。
- [... 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 of temporary to rvalue reference 谢谢@dyp for unearthing this issue

【讨论】:

  • 所以,据我了解,如果我的编译器优化了 x3 的移动,x42 和 x52 中不应该有任何移动,对吗?谢谢!
  • “如果你的编译器很好,这个动作将被省略” 只有当它没有副作用时(即不适用于X类型)。临时绑定到std::move的引用参数,这禁止复制/移动省略。
  • 很好的解释,尤其是标准中的引用我喜欢粗体部分。
  • @dyp:更正了。遗憾的是,对std::move 的调用不能简化为仅更改类型而不禁用这些优化...
  • Here it is,我认为这表明static_cast&lt;X&amp;&amp;&gt;(..) 也会防止移动省略。
【解决方案2】:

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

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 不完全:tx 不是临时的,它会被移出函数(语义上)进入返回值。 NRVO 适用,因此 move 将被省略。 (是的,尽管tx 是一个左值;这是一个特殊的语言规则)。返回值是临时的,将用于构造main中的变量,这个move也可以省略。
    • @dyp 和 luk32 谢谢你们!
    • @dyp 我认为在省略时移动和复制之间没有任何区别。但是你能解释一下ts 不是临时的吗?它的范围仅限于getTransform 在这种情况下称它为临时的不正确吗?我可以编辑答案,所以它会说“tx 在其生命周期结束时会在语义上被复制,但实际的复制操作被省略”。听起来更好/更正确吗?
    • 根据 C++ 标准,临时对象是在创建它的(完整)表达式末尾被销毁的未命名对象。 tx 里面的 getTransform 只是一个局部变量。 -- 当然,省略的移动和省略的副本之间没有太大区别。但是,如果 NRVO 被允许但未执行,tx 将被移动到返回值中。
    猜你喜欢
    • 2012-08-02
    • 2016-12-12
    • 2012-01-21
    • 1970-01-01
    • 2017-09-15
    • 1970-01-01
    • 1970-01-01
    • 2020-08-06
    相关资源
    最近更新 更多