Stephen
Stephen

Reputation: 621

What is the magic in return value optimization on this?

Basically, what I am doing is as follows. My class D has three constructors (default, move, copy) and two overloaded assignment operators (move and copy). I expected that any creation of a D type object will invoke at least one of the five.

However, creating a D object "d4" as follows does not invoke any of them:

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

Here is the code that reproduces the issue I was wondering about:

#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;
}

Basically, I assumed that, the D(D&& d) will be invoked to create d4 as foo() returns a temporary whose address may not be taken. Actually, that was true only when the return value optimization was disabled by -fno-elide-constructors.

However, if it was not specified, the RV optimization is by default turned on even on -O0. Then, what I have seen is as follows:

D default
returning a non const D

All I saw from the stdout came from foo(). Creation of d4 itself gave me nothing. This is different from what I expected.

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."

I know that might require redundant memory space. However, particularly in my example, the move constructor will die with assert(0). Whatsoever constructor but it will let the program continue. As a result, the return value optimization made difference in terms of program's output.

Is that expected? If so, what is the reason behind? I have tested both g++-7.3.0 and clang++-5.0.1. They were the same.

Upvotes: 1

Views: 190

Answers (2)

con ko
con ko

Reputation: 1416

Because you use C++17, which promises RVO, even you added -O0. this maybe help

Upvotes: 2

eerorika
eerorika

Reputation: 238491

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.

OK, good so far.

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."

Ah, but here you've assumed wrong. You see, RVO is not the only kind of copy elision. The copy initialization of the local variable from the temporary return value can also be elided, and it was. So, there is no "another memory space in the caller's stack", since the object was constructed directly into the memory location of the variable.

Is that expected?

It should be expected that copy elision can happen. It shouldn't be expected that copy elision will happen in the sense that you cannot rely on the copy / move constructor to not have side effects.

Upvotes: 2

Related Questions