Return value optimizations and side-effects

前端 未结 4 885
孤城傲影
孤城傲影 2020-12-23 22:51

Return value optimization (RVO) is an optimization technique involving copy elision, which eliminates the temporary object created to hold a function\'s return value in cert

相关标签:
4条回答
  • 2020-12-23 23:27

    I highly recommend reading "Inside the C++ Object Model" by Stanely B. Lippman for detailed information and some historical backround on how the named return value optimization works.

    For example, in chapter 2.1 he has this to say about named return value optimization:

    In a function such as bar(), where all return statements return the same named value, it is possible for the compiler itself to optimize the function by substituting the result argument for the named return value. For example, given the original definition of bar():

    X bar() 
    { 
       X xx; 
       // ... process xx 
       return xx; 
    } 
    

    __result is substituted for xx by the compiler:

    void 
    bar( X &__result ) 
    { 
       // default constructor invocation 
       // Pseudo C++ Code 
       __result.X::X(); 
    
       // ... process in __result directly 
    
       return; 
    }
    

    (....)

    Although the NRV optimization provides significant performance improvement, there are several criticisms of this approach. One is that because the optimization is done silently by the compiler, whether it was actually performed is not always clear (particularly since few compilers document the extent of its implementation or whether it is implemented at all). A second is that as the function becomes more complicated, the optimization becomes more difficult to apply. In cfront, for example, the optimization is applied only if all the named return statements occur at the top level of the function. Introduce a nested local block with a return statement, and cfront quietly turns off the optimization.

    0 讨论(0)
  • 2020-12-23 23:31

    It states it pretty clear, doesn't it? It allows to omit ctor with side effects. So you should never have side effects in ctors or if you insist, you should use techniques which eliminate (N)RVO. As to the second I believe it prohibits NRVO since std::move produces T&& and not T which would be candidate for NRVO(RVO) because std::move removes name and NRVO requires it(thanks to @DyP comment).

    Just tested the following code on MSVC:

    #include <iostream>
    
    class A
    {
    public:
        A()
        {
            std::cout << "Ctor\n";
        }
        A(const A&)
        {
            std::cout << "Copy ctor\n";
        }
        A(A&&)
        {
            std::cout << "Move\n";
        }
    
    };
    
    A foo()
    {
        A a;
        return a;
    }
    
    int main() 
    {
        A a = foo();
        return 0;
    }
    

    it produces Ctor, so we have lost side effects for move ctor. And if you add std::move to foo() you will have NRVO eliminated.

    0 讨论(0)
  • 2020-12-23 23:38

    I am used to optimizations being constrained such that they cannot change observable behaviour.

    This is correct. As a general rule -- known as the as-if rule -- compilers can change code if the change is not observable.

    This restriction does not seem to apply to RVO.

    Yes. The clause quoted in the OP gives an exception to the as-if rule and allows copy construction to be omitted, even when it has side effects. Notice that the RVO is just one case of copy-elision (the first bullet point in C++11 12.8/31).

    Do I ever need to worry about the side effects mentioned in the standard?

    If the copy constructor has side effects such that copy elision when performed causes a problem, then you should reconsider the design. If this is not your code, you should probably consider a better alternative.

    What do I as a programmer need to do (or not do) to allow this optimization to be performed?

    Basically, if possible, return a local variable (or temporary) with the same cv unqualified type as the function return type. This allows RVO but doens't enforce it (the compiler might not perform RVO).

    For example, does the following prohibit the use of copy elision (due to the move):

    // notice that I fixed the OP's example by adding <double>
    std::vector<double> foo(int bar){
        std::vector<double> quux(bar, 0);
        return std::move(quux);
    }
    

    Yes, it does because you're not returning the name of a local variable. This

    std::vector<double> foo(int bar){
        std::vector<double> quux(bar,0);
        return quux;
    }
    

    allows RVO. One might be worried that if RVO is not performed then moving is better than coping (which would explain the use of std::move above). Don't worry about that. All major compilers will do the RVO here (at least in release build). Even if a compiler doesn't do RVO but the conditions for RVO are met then it will try to do a move rather than a copy. In summary, using std::move above will certainly make a move. Not using it will likely neither copy nor move anything and, in the worst (unlikely) case, will move.

    (Update: As haohaolee's pointed out (see comments), the following paragraphs are not correct. However, I leave them here because they suggest an idea that might work for classes that don't have a constructor taking a std::initializer_list (see the reference at the bottom). For std::vector, haohaolee found a workaround.)

    In this example you can force the RVO (strict speaking this is no longer RVO but let's keep calling this way for simplicity) by returning a braced-init-list from which the return type can be created:

    std::vector<double> foo(int bar){
        return {bar, 0}; // <-- This doesn't work. Next line shows a workaround:
        // return {bar, 0.0, std::vector<double>::allocator_type{}};
    }
    

    See this post and R. Martinho Fernandes's brilliant answer.

    Be carefull! Have the return type been std::vector<int> the last code above would have a different behavior from the original. (This is another story.)

    0 讨论(0)
  • 2020-12-23 23:41
    1. This is probably obvious but if you avoid writing copy/move constructors with side effects (most have no need for them) then the problem is totally moot. Even in simple side effect cases like construction/destruction counting it should still be fine. The only case to possibly worry is complicated side effects and that's a strong design smell to re-examime your code.

    2. This sounds like premature optimization to me. Just write the obvious, easily maintainable code, and let the compiler optimize. Only if profiling shows that certain areas are performing poorly should you consider adopting changes to improve performance.

    0 讨论(0)
提交回复
热议问题