Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimally returning a copied value

Tags:

c++

I was rather surprised seeing following optimization introduced by Jonathan Wakely in libstdc++: https://gcc.gnu.org/ml/libstdc++/2018-05/txtc2I3IxLCfn.txt and tried to investigate it.

Given a couple of implementations of concat function for any non-trivial class, surprisingly only the first is optimal (one copy c-tor and one operator+= call). Others incur additional copy/move c-tor call - you can verify it at godbolt.org.

Why is that? I'd assume that all these implementation would generate same code with -O2.

Does the language specification mandate such behavior (if so why?) or is this an QoI issue?

This is consistent behavior between GCC and Clang on all versions and language versions.

void extern_copy();
void extern_move();
void extern_plus_assign();

struct myclass
{
    myclass(const myclass&)
    {
        extern_copy();
    }
    myclass(myclass&&)
    {
        extern_move();
    }
    myclass& operator+=(const myclass&)
    {
        extern_plus_assign();
        return *this;
    }
};

myclass concat(const myclass& lhs, const myclass& rhs)
{
    myclass copy(lhs);
    copy += rhs;
    return copy;
}


myclass concat2(const myclass& lhs, const myclass& rhs)
{
    myclass copy(lhs);
    return copy += rhs;
}

myclass concat3(const myclass& lhs, const myclass& rhs)
{
    return myclass(lhs) += rhs;
}

static myclass concat4impl(myclass lhs, const myclass& rhs)
{
    return lhs += rhs;
}

myclass concat4(const myclass& lhs, const myclass& rhs)
{
    return concat4impl(lhs, rhs);
}


static myclass concat5impl(myclass lhs, const myclass& rhs)
{
    lhs += rhs;
    return lhs;
}

myclass concat5(const myclass& lhs, const myclass& rhs)
{
    return concat5impl(lhs, rhs);
}

Update: code modified to exclude problem with following implementation of operator+=:

myclass& myclass::operator+=(myclass const& v) {
  static myclass weird;
  return weird;
}
like image 511
Maciej Cencora Avatar asked May 08 '18 14:05

Maciej Cencora


People also ask

What is the advantage of returning a reference from the function?

Returning a reference returns by reference. In addition to allowing modification of the returned object, this does not make a copy and saves whatever effort would have been consumed by making a copy.

Are return values Rvalues?

Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue. Once you call return on an object, the name of the object does not exist anymore (it goes out of scope), so it becomes an rvalue.

How do I return a const reference?

You want to return a const reference when you return a property of an object, that you want not to be modified out-side of it. For example: when your object has a name, you can make following method const std::string& get_name(){ return name; }; . Which is most optimal way.


1 Answers

Copy elision (RVO, NRVO, and a special case of initialization) is a very special optimization: it is the only optimization in C++ that is not covered by the AS-IF rule. With every other optimization, the compiler simply modifies the program in a way that does not change the observable behavior, only the performance. But RVO can change observable behavior, because side effects in the elided copy constructor and destructor calls (such as output) are lost. This is why there is a special license in the standard for the compiler to perform this optimization, under very specific circumstances.

Specifically, RVO applies only if the return value is a prvalue, and NRVO applies only if the return expression trivially refers to a local variable.

The return expression in the statement return copy += rhs; is neither. It does not trivially refer to a local variable (it's a compound assignment expression, not an id-expression), and the return value is not a prvalue (your += overload returns an lvalue reference, making the value an lvalue; this would be different if the operator returned by value, but that would be highly unidiomatic otherwise).

You may think that the compiler could inline the operator and discover that it's the same object, but the permission to perform this optimization is not given on this level.

like image 192
Sebastian Redl Avatar answered Nov 10 '22 05:11

Sebastian Redl