10

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

galinette
  • 8,896
  • 2
  • 36
  • 87
  • 1
    Can you please clarify your question? Do you often return virtual functions? – juanchopanza Oct 09 '14 at 13:24
  • 1
    @juanchopanza: I think the question is whether RVO works _within_ a virtual function, i.e. for anything the virtual function might return, not whether RVO works when returning a virtual function. (And I don't see a reason why that shouldn't in principle work) – Damon Oct 09 '14 at 13:28
  • @Damon I think the same, but better let OP explain what they actually mean to ask. – juanchopanza Oct 09 '14 at 13:31
  • The virtual mechanism is just there to select which function gets called. After that selection I would think whatever actual function is selected will have RVO compiled into it if appropriate. I don't see that the compiler needs to know what the other functions do when dealing with each specific function. – Galik Oct 09 '14 at 13:41
  • I do not know how RVO works, but I would have guessed that the linker must know if RVO occurs or not. Is the RVO mechanism totally transparent to the caller? – galinette Oct 09 '14 at 13:50
  • It depends on whether your ABI specifies a calling convention that is compatible with RVO, or RVO is a custom calling convention (implemented as a form of partial inlining). – Ben Voigt Oct 09 '14 at 13:53
  • In the end, RVO boils down to the same thing as move construction. You have an object of which the compiler knows that it's going to be destroyed, and someone else wants a copy of it. Since the compiler knows nobody will notice the object missing, it can do a _Cansas City Shuffle_, and nobody ever notices that they've been cheated. I can see how it may be a bit more work for the compiler if one version of a virtual function allows for RVO and another one doesn't, but I don't think it's forbidding in general. – Damon Oct 09 '14 at 15:20
  • @juanchopanza : I have added a detailed description, and the question was put on hold shortly after. Is it still unclear? – galinette Oct 09 '14 at 16:55
  • @galinette Yes, it is fine now. – juanchopanza Oct 09 '14 at 17:43
  • @galinette Whil people are completely right in pointing out that RVO implementation is not defined in the standard, on MSVC and g++ on 32 and 64 Bit x86 systems, RVO is indeed an optimization that is purely applied inside the function and transparent to the caller. – Michael Karcher Oct 27 '14 at 00:29

2 Answers2

5

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
1

If you return std::move(result);, you cannot gain anything, and you can lose. So don't do it.

You cannot gain anything, because the standard explicitly says "if RVO conditions are met, or you're returning a parameter, try returning as rvalue first and only if that wouldn't compile, return as lvalue. So even if you return result;, the compiler is forced to try return std::move(result); first.

You can lose, since return std::move(result); specifically prevents RVO if it was otherwise applicable.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Actually, in cases RVO is not possible, and if the returned type is movable and has a large underlying buffer, you can gain a lot. This is also not an answer to the question. – galinette Oct 09 '14 at 13:48
  • 2
    @galinette No, if copy elision can't be performed for whatever reason, `return result;` will still perform a move when possible. – T.C. Oct 09 '14 at 13:50
  • @galinette Well, if RVO is not possible, then `result` is neither a local variable nor a parameter of the function, so using `std::move` is not a no-brainer in this case anyway. You were asking about RVO, so I assumed a situation where RVO criteria are met. – Angew is no longer proud of SO Oct 09 '14 at 13:53
  • @galinette Regarding the example you've added to the question, even if RVO doesn't happen (it's always optional), the *criteria for RVO are met,* and therefore the compiler must try the rvalue overloads first. – Angew is no longer proud of SO Oct 09 '14 at 13:56