Search code examples
c++virtualmovervo

Can a virtual function be a candidate to RVO (return value optimization)?


Are C++ compilers able to apply RVO for virtual functions?

In this case:

class AbstractReader
{
//...
public:
    virtual std::vector<float> getFloatVector() = 0;
//...
}

class XmlReader : public AbstractReader
{
//...
public:
    virtual std::vector<float> getFloatVector()
    {
        std::vector<float> result;

        //Do some parsing here...

        return result;
    }
//...
}



class BinaryReader : public AbstractReader
{
//...
public:
    virtual std::vector<float> getFloatVector()
    {
        std::vector<float> result;

        //Do some decoding here...

        return result;
    }
//...
}

Can RVO apply to return result; lines? I would guess not.

Then, is std::move(result) the way to go for returning large containers in that case?

Thanks


Solution

  • Yes, the compiler can perform RVO. I cooked up some testing code and ran it through godbolt:

    struct M {
      M();
      M(const M&);
      M(M &&);
      ~M();
      double * ptr;
    };
    
    M getM();
    
    struct A {
      virtual M foo() = 0;
    };
    
    struct B : A {
      virtual M foo() override;
    };
    
    M B::foo(){
      M m;
      return m;
    }
    
    struct C : B {
      virtual M foo() override;
    };
    M C::foo(){
      M m = getM();
      return m;
    }
    
    A* getA();
    
    int main(){
      A* p = getA();
      M m = p->foo();
    }
    

    g++ -O3 produces

    B::foo():
        pushq   %rbx
        movq    %rdi, %rbx
        call    M::M()
        movq    %rbx, %rax
        popq    %rbx
        ret
    C::foo():
        pushq   %rbx
        movq    %rdi, %rbx
        call    getM()
        movq    %rbx, %rax
        popq    %rbx
        ret
    main:
        subq    $24, %rsp
        call    getA()
        movq    (%rax), %rdx
        movq    %rax, %rsi
        movq    %rsp, %rdi
        call    *(%rdx)
        movq    %rsp, %rdi
        call    M::~M()
        xorl    %eax, %eax
        addq    $24, %rsp
        ret
    

    Conspicuously absent from the disassembly is any call to the copy or move constructor of M.


    Also, the paragraph of the standard setting out the criteria for copy elision draws no distinction between virtual and nonvirtual member functions, and whenever the standard for copy elision is met, overload resolution for the return statement "is first performed as if the object were designated by an rvalue".

    That is to say, in a function

    M foo() {
        M m = /*...*/;
        return m;
    }
    

    If copy elision can't take place for whatever reason, and a move constructor is available, return m; will always invoke the move constructor rather than the copy constructor. Hence, there's no need to use std::move for the return statement if you are returning a local variable.