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
One form of copy elision is known as "named return value optimization" - a.k.a NRVO. Another one is known as "return value optimization" - a.k.a RVO. There are also other forms of copy elision which don't involve returned values.
Virtual functions are slow when you have a cache miss looking them up. As we'll see through benchmarks, they can be very slow. They can also be very fast when used carefully — to the point where it's impossible to measure the overhead.
In the context of the C++ programming language, return value optimization (RVO) is a compiler optimization that involves eliminating the temporary object created to hold a function's return value. RVO is allowed to change the observable behaviour of the resulting program by the C++ standard.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With