He cambiado el código un poco para probarlo, y para mí parece que está dejando caer el vtable, pero no soy lo suficientemente experto como para contarlo. Estoy seguro que algunos comentaristas me van a arreglar sin embargo :)
struct A {
virtual int foo() { return 1; }
};
struct B : public A {
virtual int foo() { return 2; }
};
int useIt(A* a) {
return a->foo();
}
int main()
{
B* b = new B();
return useIt(b);
}
que luego se convierte este código para el montaje de esta manera:
g++ -g -S -O0 -fverbose-asm virt.cpp
as -alhnd virt.s > virt.base.asm
g++ -g -S -O6 -fverbose-asm virt.cpp
as -alhnd virt.s > virt.opt.asm
y los bits interesantes miran a mí como el 'opt' versión está soltando el vtable Parece que se está creando la viable, pero no usarlo ..
En el asm opt:
9:virt.cpp **** int useIt(A* a) {
89 .loc 1 9 0
90 .cfi_startproc
91 .LVL2:
10:virt.cpp **** return a->foo();
92 .loc 1 10 0
93 0000 488B07 movq (%rdi), %rax # a_1(D)->_vptr.A, a_1(D)->_vptr.A
94 0003 488B00 movq (%rax), %rax # *D.2259_2, *D.2259_2
95 0006 FFE0 jmp *%rax # *D.2259_2
96 .LVL3:
97 .cfi_endproc
y la base.Versión asm de la misma:
9:virt.cpp **** int useIt(A* a) {
88 .loc 1 9 0
89 .cfi_startproc
90 0000 55 pushq %rbp #
91 .LCFI6:
92 .cfi_def_cfa_offset 16
93 .cfi_offset 6, -16
94 0001 4889E5 movq %rsp, %rbp #,
95 .LCFI7:
96 .cfi_def_cfa_register 6
97 0004 4883EC10 subq $16, %rsp #,
98 0008 48897DF8 movq %rdi, -8(%rbp) # a, a
10:virt.cpp **** return a->foo();
99 .loc 1 10 0
100 000c 488B45F8 movq -8(%rbp), %rax # a, tmp64
101 0010 488B00 movq (%rax), %rax # a_1(D)->_vptr.A, D.2263
102 0013 488B00 movq (%rax), %rax # *D.2263_2, D.2264
103 0016 488B55F8 movq -8(%rbp), %rdx # a, tmp65
104 001a 4889D7 movq %rdx, %rdi # tmp65,
105 001d FFD0 call *%rax # D.2264
11:virt.cpp **** }
106 .loc 1 11 0
107 001f C9 leave
108 .LCFI8:
109 .cfi_def_cfa 7, 8
110 0020 C3 ret
111 .cfi_endproc
En la línea 93 que vemos en los comentarios: _vptr.A
cual estoy bastante seguro de que quiere decir que está haciendo una búsqueda vtable, sin embargo, en la función principal real, que parece ser capaz de predecir la respuesta y ni siquiera llamar a ese código Useit:
16:virt.cpp **** return useIt(b);
17:virt.cpp **** }
124 .loc 1 17 0
125 0015 B8020000 movl $2, %eax #,
que creo que es sólo decir, sabemos que vamos a retorno 2, permite a ponerlo en EAX. (Ejecuté el programa pidiéndole que devolviera 200, y esa línea se actualizó como era de esperar).
poco más
Así que complica el programa un poco más:
struct A {
int valA;
A(int value) : valA(value) {}
virtual int foo() { return valA; }
};
struct B : public A {
int valB;
B(int value) : valB(value), A(0) {}
virtual int foo() { return valB; }
};
int useIt(A* a) {
return a->foo();
}
int main()
{
A* a = new A(100);
B* b = new B(200);
int valA = useIt(a);
int valB = useIt(a);
return valA + valB;
}
En esta versión, el código Useit definitivamente utiliza la viable en el montaje optimizado:
13:virt.cpp **** int useIt(A* a) {
89 .loc 1 13 0
90 .cfi_startproc
91 .LVL2:
14:virt.cpp **** return a->foo();
92 .loc 1 14 0
93 0000 488B07 movq (%rdi), %rax # a_1(D)->_vptr.A, a_1(D)->_vptr.A
94 0003 488B00 movq (%rax), %rax # *D.2274_2, *D.2274_2
95 0006 FFE0 jmp *%rax # *D.2274_2
96 .LVL3:
97 .cfi_endproc
Esta vez, la función principal en línea una copia de useIt
, pero realmente hace la búsqueda vtable.
¿Qué hay de C++ 11 y la palabra clave 'final'?
Así que cambió una línea a:
virtual int foo() override final { return valB; }
y la línea compilador:
g++ -std=c++11 -g -S -O6 -fverbose-asm virt.cpp
Pensando que decirle al compilador que se trata de una anulación definitiva, permitiría que salte la vtable tal vez.
Resulta que todavía usa el vtable.
Así que mi respuesta teórica sería:
- No creo que hay alguna, "no usar la viable" optimizaciones explícitos. (Busqué en la página de manual de g ++ vtable y virt y similares y no encontré nada).
- Pero g ++ con -O6, puede hacer una gran cantidad de optimización en un programa simple con constantes obvias hasta el punto en que puede predecir el resultado y omitir la llamada por completo.
- Sin embargo, una vez que las cosas se vuelven complejas (léales), definitivamente está haciendo búsquedas vtable, prácticamente cada vez que llamas a una función virtual.
¿Está tratando de optimizar (no pierda su tiempo que es el trabajo de los compiladores). ¿O quieres una técnica para simplemente llamar a la versión de B de foo()? –
No debería preocuparse si el envío será directo o irá a través de un vtable. En la mayoría de los escenarios, el envío de tablas de métodos virtuales casi nunca tendrá un impacto significativo en el rendimiento. –