return 价值优化的魔力是什么?

What is the magic in return value optimization on this?

基本上,我正在做的事情如下。我的 class 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&& d) 将被调用以创建 d4 作为 foo() returns 一个临时地址可能不会被占用。实际上,只有当 return 值优化被 -fno-elide-constructors 禁用时才会如此。

但是,如果未指定,即使在 -O0 上,RV 优化也会默认打开。那么,我看到的是这样的:

D default
returning a non const D

我从标准输出中看到的所有内容都来自 foo()。 d4 本身的创建没有给我任何帮助。这与我的预期不同。

我预计会出现以下情况。 returned 值的内存 space 分配在调用者的堆栈中,而不是被调用者的堆栈中。调用默认构造函数来访问内存space。从被调用者的堆栈到调用者的堆栈不会发生复制。之后,另一个内存 space 被分配到调用者的堆栈中。由于 returned 值是一个右值,因此调用移动构造函数在 "another memory space in the caller's stack."

上写一些东西

我知道这可能需要冗余内存 space。但是,特别是在我的示例中,移动构造函数将因 assert(0) 而终止。任何构造函数,但它会让程序继续。因此,return 值优化在程序输出方面产生了差异。

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

因为你使用 C++17,它承诺 RVO,即使你添加了 -O0。 this maybe help

I expected the following. A memory space for the returned value is allocated in the caller's stack rather than the callee's stack. The default constructor is called to touch the memory space. No copy will happen from the callee's stack to the caller's stack.

好的,到目前为止还不错。

Following that, another memory space is allocated in the caller's stack. As the returned value is an rvalue, the move constructor is called to write something on the "another memory space in the caller's stack."

啊,但是这里你假设错了。你看,RVO 不是唯一的一种复制省略。从临时 return 值复制局部变量的初始化也可以省略,确实如此。所以,没有"another memory space in the caller's stack",因为对象是直接构造到变量的内存位置。

Is that expected?

应该预料到会发生复制省略。在您不能依赖复制/移动构造函数到没有有副作用的意义上,不应期望复制省略会发生。