Sì, tipo di. Poiché i tuoi metodi sono definiti in linea, a volte possono essere sottolineati.
Né Clang né GCC creano un B::ManyFoo(int)
specializzato, però.
Ho modificato il codice per evitare ottimizzazioni inadatte e illustrare alcuni comportamenti:
struct A {
virtual int Foo() = 0;
virtual int ManyFoo(int N) {
int res = 0;
for (int i = 0; i < N; ++i) res += Foo();
return res;
}
};
struct B : A {
virtual int Foo() { return 3; }
};
B force_code_generation() { return {}; }
int dynamic_dispatch(A& object) { return object.ManyFoo(2); }
int static_dispatch (B value) { return value .ManyFoo(2); }
In un sito non virtuale dove il compilatore è in grado di eseguire la spedizione statica,
ManyFoo()
e Foo()
possono essere completamente appiattiti. GCC 8.2 con -O2 è in grado di valutare la funzione in fase di compilazione:
mov eax, 6
ret
Ma Clang non sembra fare questa ottimizzazione. Integra semplicemente la chiamata ManyFoo(2)
, che utilizza chiamate virtuali per richiamare Foo()
. Pseudo-codice:
static_dispatch(B* rdi):
push rbp
push rbx
push rax
rbx = rdi
rax = *rbx // load vtable
eax = call rax[0](rdi) // first Foo() call
ebp = eax
rax = *rbx // load vtable
rdi = rbx // move this pointer to rdi
eax = call rax[0](rdi)
eax += ebp // add the Foo() results
rsp += 8 // discard saved rax
pop rbx
pop rbp
return eax
Con la spedizione dinamica queste ottimizzazioni non sono generalmente possibili. Clang non aggiunge ottimizzazioni speciali e utilizza semplicemente le normali chiamate virtuali. Tuttavia, GCC 8.2 aggiunge guardie ai callites virtuali per integrare facoltativamente la funzione virtuale. Ecco l'assembly generato riscritto come pseudo-codice e riordinato per chiarezza:
dynamic_dispatch(A& rdi):
rax = *rdi // load vtable from object
rdx = rax[8] // load ManyFoo(int) vtable entry
// check if ManyFoo(int) method is A::ManyFoo(int)
if (rdx != &A::ManyFoo(int)) {
// fallback for virtual ManyFoo(2) call, and return
esi = 2
goto rdx // tailcall
}
// We are now in the specialized A::ManyFoo(2) version.
// The loop for N=2 is unrolled.
push rbp
push rbx
rsp -= 8
// first Foo() call:
// check if Foo() is B::Foo(), else fall back to virtual call
ebx = 3 // result of the first B::Foo() call if it is inlined
rax = rax[0] // load Foo() vtable entry
if (rax != &B::Foo()) {
// fallback for first virtual Foo() call
rbp = rdi
eax = call rax(rdi)
ebx = eax // save result of first call
// second Foo() call:
// check again if Foo() is B::Foo()
// Can "this" even change its type???
rax = *rbp
rax = rax[0]
if (rax != &B::Foo()) {
// fallback for second virtual Foo() call
rdi = rbp
eax = call rax(rdi)
goto end
}
}
eax = 3 // result of second B::Foo() call
end:
// add the result of the calls and return
eax += ebx
rsp += 8
pop rbx
pop rbp
return eax
Né Clang né GCC cambiano il codice generato a seconda che B
sia final
.
Fonte: visualizza l'assembly su Explorer del compilatore Godbolt .