返回值优化的神奇之处是什么?

时间:2018-04-06 13:02:10

标签: c++ constructor return-value-optimization

基本上,我正在做的事情如下。我的D类有三个构造函数(默认,移动,复制)和两个重载的赋值运算符(移动和复制)。我预计任何D类型对象的创建都会调用五个对象中的至少一个。

然而,创建一个D对象" d4"如下所示不会调用其中任何一个:

D d4( foo() );  // foo returns a D

以下是重现我想知道的问题的代码:

#include <iostream>
#include <vector>
#include <cassert>
using std::cout;
using std::endl;
    class D {
public:
    D()
    { cout << "D default"<<endl;}

    D(const D& d)
    {
        cout << "D copy" << endl;
    }

    D(D&& d)
    {
        cout << "D rval" << endl;
        assert(0);
    }

    D& operator=(D&& d)
    {
        cout << "D mv assign" << endl;
        return *this;
    }

    D& operator=(const D& d)
    {
        cout << "D copy assign" << endl;
        return *this;
    }

    volatile int v;
};

// return 
D foo()
{
    D res;
    cout <<"returning a non const D" << endl;
    return res;
}

int main()
{
    D d4(foo());

    return 0;
}

基本上,我假设,D(D&amp;&amp; d)将被调用以创建d4,因为foo()返回一个临时的,其地址可能不被采用。实际上,只有当-fno-elide-constructors禁用了返回值优化时才会出现这种情况。

但是,如果未指定,则默认情况下即使在-O0上也会启用RV优化。然后,我所看到的如下:

D default
returning a non const D

我从stdout看到的所有内容都来自foo()。创造d4本身并没有给我什么。这与我的预期不同。

我期待以下内容。返回值的存储空间在调用者的堆栈中而不是被调用者的堆栈中分配。调用默认构造函数来触摸内存空间。从被叫方的堆栈到呼叫者的堆栈不会发生任何副本。之后,在呼叫者的堆栈中分配另一个存储空间。由于返回的值是一个rvalue,因此调用move构造函数在&#34;调用者堆栈中的另一个内存空间上写一些东西。&#34;

我知道可能需要冗余的内存空间。但是,特别是在我的示例中,移动构造函数将死于assert(0)。无论什么构造函数,但它会让程序继续。结果,返回值优化在程序的输出方面产生了差异。

这是预期的吗?如果是这样,背后的原因是什么?我测试了g ++ - 7.3.0和clang ++ - 5.0.1。他们是一样的。

2 个答案:

答案 0 :(得分:2)

因为你使用C ++ 17,它有时会承诺RVO,甚至你添加-O0。 this maybe help

答案 1 :(得分:2)

  

我期待以下内容。返回值的内存空间分配在调用者的堆栈中,而不是被调用者的堆栈中。调用默认构造函数来触摸内存空间。从被调用者的堆栈到调用者的堆栈不会发生任何副本。

好的,到目前为止很好。

  

然后,在调用者的堆栈中分配另一个内存空间。由于返回的值是rvalue,因此调用移动构造函数在“调用者堆栈中的另一个内存空间”上写入内容。

啊,但是你错了。你看,RVO并不是唯一的复制版本。从临时返回值复制初始化局部变量也可以省略,并且它是。因此,没有“调用者堆栈中的另一个内存空间”,因为该对象直接构造在变量的内存位置。

  

这是预期的吗?

应该可以预期复制省略可能发生。不应期望复制省略会发生,因为你不能依赖复制/移动构造函数有副作用。