Nov 4, 2018

[cppcon 2018] Return Value Optimization: Harder Than It Looks - Arthur O'Dwyer

With this talk, Arthur tried to focus on not only RVO but what if RVO doesn't work, what mechanisms kick in and how it effects our design.


'return slot' definition
iff the function returning type's size is larger than %eax, the compiler will pass in extra parameter to the callee function which stores the pointer to the caller's pre-allocated address which is large enough to store the callee's returning type's value.
Caller owns the return slot address.


Copy elision rules in C++
  • C++03/14
  • C++17
    • Mandatory in many circumstances
  • Cases RVO not possible


RVO Limitation (which can't do RVO)
  • callee has branches
  • returning callee's argument
  • returning a global/namespace level varaible
  • returning a derived type of returning type
Even though above cases can't be RVOed, the compiler
will try hard to do you an implicit move.
But, if it just can NOT being moved, copy instead.

Digress a bit here:


Beware:
Overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. (Thus do a copy through copy constructor)

RVO/Move works on constructor, not on conversion operators.

Why RVO works on constructor that takes rvalue reference (move) / const reference (copy constructor, C++98)?
Because we have unique_ptr
Why RVO doesn't work on conversion operators?
Auh, KISS.
// Works due to unique_ptr has template move constructor, which allows RVO.
// Since unique_ptr does not allow being copied.
unique_ptr<base> Call()
{
    unique_ptr<derived> de{new Derived{}};
    return de;                              
}


Types that have move constructor which enables RVO in the standard:
(i.e the return type isn't same as function return signature type)


While the callee's parameter is reference, always do a copy, never move.
(Since you don't know what it refers to)

Base Call(Base&& b){
    return b;
}


Base Call(Base& b){
    return b;
}


Base Call(Base& b){
    Base&& x = std::move(b);
    return x;
}

What about this one:
// Compiler will either do a Move or a Copy.
Base Call(Base b){
    b += 1;
    return b;
}


Other Take aways:
  • When returning lambda, consider designing the lambda which can be RVOed.
  • Don't do std::move by yourself,
    i.e DO NOT DO
    return std::move(x);
    which will disable RVO/implicit move.
  • When dealing with legacy C code, use C++ as a wrapper, which gives as value semantics(RAII style).
  • In clang, use -Wall to enable -Wreturn-std-move

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.