Suppose you have this simple class hierarchy:
struct base {
virtual void f () const = 0;
};
struct derived : public base {
virtual void f () const final
{
...
}
};
There is only one implementation of f(), and because it is declared final, may we consider that there is no polymorphism ?
If so, will the compiler optimize code by avoiding use of a virtual table since 'You don't pay what you don't use ?'
Thank you.
CodePudding user response:
There is a polymorphism because there is a virtual function and you may define other derived classes from the base class.
From the C 17 Standard (13.3 Virtual functions)
1 [ Note: Virtual functions support dynamic binding and object-oriented programming. — end note ] A class that declares or inherits a virtual function is called a polymorphic class.
CodePudding user response:
will the compiler optimize code by avoiding use of a virtual table
If the compiler has a pDerived->f()
then yes, this is what it typically will do because this is what final
is designed for in the first place.
If it has a pBase->f()
, then such optimisation is only possible if the compiler can prove that pBase
points to a derived
and not any other class derived from base
. f
being or not being declared final
is irrelevant for this analysis. Note that derived classes can come from different translation units. The compiler normally only sees one translation unit at a time, so it needs some kind of data flow analysis to eliminate this possibility. Link-time optimisations do not really help here because additional modules can be loaded at run time, and the link time optimiser cannot see them.
CodePudding user response:
I made an example in compiler explorer: https://godbolt.org/z/exsx8dzra
The code:
struct base {
virtual int f () const = 0;
};
struct derived : public base {
virtual int f () const final { return 2; }
};
int returnf(const base& b)
{
return b.f();
}
The Assembly (x86-64 gcc12.2, -std=c 20 -O3) :
main:
xor eax,eax
ret
cs nop WORD PTR [rax rax*1 0x0]
nop DWORD PTR [rax]
returnf(base const&):
mov rax,QWORD PTR [rdi]
mov rax,QWORD PTR [rax]
cmp rax,0x401140
jne 401138 <returnf(base const&) 0x18>
mov eax,0x2
ret
nop DWORD PTR [rax 0x0]
jmp rax
nop WORD PTR [rax rax*1 0x0]
derived::f() const:
mov eax,0x2
ret
cs nop WORD PTR [rax rax*1 0x0]
Here you can see in the assembly that the virtual function call is not completely optimized away, namely there still is a check if the object is of the type derived:
cmp rax,0x401140
jne 401138 <returnf(base const&) 0x18>
If so it returns the value specified in the method, otherwise it jumps to rax