I wrote this short program to see how devirtualization would work. The compiler should be able to deduce the correct type:
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
void foo() { cout << "Base::foo" << endl; }
virtual void bar() { cout << "Base::bar" << endl; }
virtual ~Base() = default;
};
class Child : public Base
{
public:
void foo() { cout << "Child::foo" << endl; }
void bar() { cout << "Child::bar" << endl; }
};
int main()
{
Base* obj = new Child;
obj->foo();
obj->bar();
delete obj;
}
Compiled with -O2 -std=c++11
using gcc 5.3 and clang 3.7 via https://gcc.godbolt.org/.
What turned out is that neither compiler was able to optimize everything - gcc inlines foo()
and makes virtual call to bar()
while clang makes call to foo()
and devirtualizes and inlines call to bar()
.
Meanwhile, if instead I call obj->bar();
and then obj->foo();
, the compilers have no problem in optimizing - clang inlines both calls and gcc makes normal call to bar()
instead of virtual one and inlines foo()
.
Can anyone explain this behavior?
It's probably because the compiler thinks that inlining does not help because the cout
is too expensive compared to the overhead of the function call. If you replace it with something simpler, e.g. an assigment to a member, it will get inlined. See below for the output of
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
void foo() { i = 1; }
virtual void bar() { i = 2; }
virtual ~Base() = default;
int i = 0;
};
class Child : public Base
{
public:
void foo() { i = 3; }
void bar() { i = 4; }
};
int main()
{
Base* obj = new Child;
obj->foo();
obj->bar();
std::cout << obj->i << std::endl;
//delete obj;
}
Assembly:
Base::bar():
movl $2, 8(%rdi)
ret
Child::bar():
movl $4, 8(%rdi)
ret
Base::~Base():
ret
Child::~Child():
ret
Child::~Child():
jmp operator delete(void*)
Base::~Base():
jmp operator delete(void*)
main:
subq $8, %rsp
movl $16, %edi
call operator new(unsigned long)
movl $4, %esi
movl std::cout, %edi
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
movq %rax, %rdi
call std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
xorl %eax, %eax
addq $8, %rsp
ret
subq $8, %rsp
movl std::__ioinit, %edi
call std::ios_base::Init::Init()
movl $__dso_handle, %edx
movl std::__ioinit, %esi
movl std::ios_base::Init::~Init(), %edi
addq $8, %rsp
jmp __cxa_atexit
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