I have a pure abstract base and two derived classes:
struct B { virtual void foo() = 0; };
struct D1 : B { void foo() override { cout << "D1::foo()" << endl; } };
struct D2 : B { void foo() override { cout << "D1::foo()" << endl; } };
Does calling foo
in Point A cost the same as a call to a non-virtual member function? Or is it more expensive than if D1 and D2 wouldn't have derived from B?
int main() {
D1 d1; D2 d2;
std::vector<B*> v = { &d1, &d2 };
d1.foo(); d2.foo(); // Point A (polymorphism not necessary)
for(auto&& i : v) i->foo(); // Polymorphism necessary.
return 0;
}
Answer: the answer of Andy Prowl is kind of the right answer, I just wanted to add the assembly output of gcc (tested in godbolt: gcc-4.7 -O2 -march=native -std=c++11). The cost of the direct function calls is:
mov rdi, rsp
call D1::foo()
mov rdi, rbp
call D2::foo()
And for the polymorphic calls:
mov rdi, QWORD PTR [rbx]
mov rax, QWORD PTR [rdi]
call [QWORD PTR [rax]]
mov rdi, QWORD PTR [rbx+8]
mov rax, QWORD PTR [rdi]
call [QWORD PTR [rax]]
However, if the objects don't derive from B
and you just perform the direct call, gcc will inline the function calls:
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
This could enable further optimizations if D1
and D2
don't derive from B
so I guess that no, they are not equivalent (at least for this version of gcc with these optimizations, -O3 produced a similar output without inlining). Is there something preventing the compiler from inlining in the case that D1
and D2
do derive from B
?
"Fix": use delegates (aka reimplement virtual functions yourself):
struct DG { // Delegate
std::function<void(void)> foo;
template<class C> DG(C&& c) { foo = [&](void){c.foo();}; }
};
and then create a vector of delegates:
std::vector<DG> v = { d1, d2 };
this allows inlining if you access the methods in a non-polymorphic way. However, I guess accessing the vector will be slower (or at least as fast because std::function
uses virtual functions for type erasure) than just using virtual functions (can't test with godbolt yet).
D1
andD2
are derived fromB
for the direct calls. – UtoaztecanD1::foo()
,D2::foo()
. It's someGCC 4.7
and above glitch.GCC 4.5
inlined this with no problems.clang 3.4.1
inlined this as well. – Sledfinal
, it is very hard to inline these even with LTO, since you can always create a new TU in which you derive from a class (and a dynamic library could do so too). IIRC Herb Sutter described the issue as "with virtual inheritance you pay for infinite extensibility", and that has a cost. – Darenfoo
during runtime. Correct me if I'm wrong. – Sled