Como dijo otra persona, el Estándar C++ no ordena una tabla de método virtual, pero permite que se use una. He hecho mis pruebas utilizando gcc y el código y uno de los escenarios más simple posible:
class Base {
public:
virtual void bark() { }
int dont_do_ebo;
};
class Derived1 : public Base {
public:
virtual void bark() { }
int dont_do_ebo;
};
class Derived2 : public Base {
public:
virtual void smile() { }
int dont_do_ebo;
};
void use(Base*);
int main() {
Base * b = new Derived1;
use(b);
Base * b1 = new Derived2;
use(b1);
}
de datos son miembros Agregado para evitar que el compilador para dar la clase base un tamaño de cero (que se conoce como la clase vacía-base-optimización). Este es el diseño que eligió GCC: (imprimir utilizando -fdump de clase-jerarquía)
Vtable for Base
Base::_ZTV4Base: 3u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI4Base)
8 Base::bark
Class Base
size=8 align=4
base size=8 base align=4
Base (0xb7b578e8) 0
vptr=((& Base::_ZTV4Base) + 8u)
Vtable for Derived1
Derived1::_ZTV8Derived1: 3u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI8Derived1)
8 Derived1::bark
Class Derived1
size=12 align=4
base size=12 base align=4
Derived1 (0xb7ad6400) 0
vptr=((& Derived1::_ZTV8Derived1) + 8u)
Base (0xb7b57ac8) 0
primary-for Derived1 (0xb7ad6400)
Vtable for Derived2
Derived2::_ZTV8Derived2: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI8Derived2)
8 Base::bark
12 Derived2::smile
Class Derived2
size=12 align=4
base size=12 base align=4
Derived2 (0xb7ad64c0) 0
vptr=((& Derived2::_ZTV8Derived2) + 8u)
Base (0xb7b57c30) 0
primary-for Derived2 (0xb7ad64c0)
Al ver cada clase tiene un vtable. Las primeras dos entradas son especiales. El segundo apunta a los datos RTTI de la clase. El primero, lo sabía, pero lo olvidé. Tiene algún uso en casos más complicados. Bueno, como muestra el diseño, si tiene un objeto de clase Derived1, entonces el vptr (v-table-pointer) apuntará a la tabla v de la clase Derived1, por supuesto, que tiene exactamente una entrada para su función ladrar apuntando a La versión de Derived1. El vptr de Derived2 apunta al vtable de Derived2, que tiene dos entradas. El otro es el nuevo método que se agrega, sonríe. Repite la entrada para Base :: bark, que apuntará a la versión de base de la función, por supuesto, porque es la versión más derivada de la misma.
También he volcado el árbol generado por GCC después de algunas optimizaciones (constructor en línea, ...), con -fdump-tree-optimized. La salida está usando el lenguaje de gama media de GCC GIMPL
que es de interfaces independientes, con una sangría en alguna estructura de bloques similar a C:
;; Function virtual void Base::bark() (_ZN4Base4barkEv)
virtual void Base::bark() (this)
{
<bb 2>:
return;
}
;; Function virtual void Derived1::bark() (_ZN8Derived14barkEv)
virtual void Derived1::bark() (this)
{
<bb 2>:
return;
}
;; Function virtual void Derived2::smile() (_ZN8Derived25smileEv)
virtual void Derived2::smile() (this)
{
<bb 2>:
return;
}
;; Function int main() (main)
int main()()
{
void * D.1757;
struct Derived2 * D.1734;
void * D.1756;
struct Derived1 * D.1693;
<bb 2>:
D.1756 = operator new (12);
D.1693 = (struct Derived1 *) D.1756;
D.1693->D.1671._vptr.Base = &_ZTV8Derived1[2];
use (&D.1693->D.1671);
D.1757 = operator new (12);
D.1734 = (struct Derived2 *) D.1757;
D.1734->D.1682._vptr.Base = &_ZTV8Derived2[2];
use (&D.1734->D.1682);
return 0;
}
Como podemos ver muy bien, es sólo la creación de un puntero - VPTR - que será apunte al vtable apropiado que hemos visto antes al crear el objeto. También he abandonado el código de ensamblador para la creación de Derived1 y call to use ($ 4 es el primer argumento de registro, $ 2 es return value register, $ 0 es always-0-register) después de demandar los nombres en él por la herramienta c++filt
:)
# 1st arg: 12byte
add $4, $0, 12
# allocate 12byte
jal operator new(unsigned long)
# get ptr to first function in the vtable of Derived1
add $3, $0, vtable for Derived1+8
# store that pointer at offset 0x0 of the object (vptr)
stw $3, $2, 0
# 1st arg is the address of the object
add $4, $0, $2
jal use(Base*)
¿Qué pasa si queremos llamar bark
:?
void doit(Base* b) {
b->bark();
}
Gimpl código:
;; Function void doit(Base*) (_Z4doitP4Base)
void doit(Base*) (b)
{
<bb 2>:
OBJ_TYPE_REF(*b->_vptr.Base;b->0) (b) [tail call];
return;
}
OBJ_TYPE_REF
es una GIMP L constructo que prácticamente se imprime en (se documenta en gcc/tree.def
en el gcc código fuente SVN)
OBJ_TYPE_REF(<first arg>; <second arg> -> <third arg>)
su significado: Utilice la expresión *b->_vptr.Base
en el objeto b
, y almacenar el frontend (C++) valor específico 0
(es el índice en el vtable). Finalmente, está pasando b
como el argumento "this". ¿Llamaríamos a una función que aparece en el segundo índice en el vtable (nota, no sabemos qué variable de qué tipo?), El Gimpl se vería así:
OBJ_TYPE_REF(*(b->_vptr.Base + 4);b->1) (b) [tail call];
Por supuesto, aquí el código ensamblador de nuevo (cosas apilar-marco cortada):
# load vptr into register $2
# (remember $4 is the address of the object,
# doit's first arg)
ldw $2, $4, 0
# load whatever is stored there into register $2
ldw $2, $2, 0
# jump to that address. note that "this" is passed by $4
jalr $2
Recuerde que los puntos VPTR exactamente en la primera función . (Antes de esa entrada, se almacenaba la ranura RTTI). Entonces, lo que aparece en esa ranura se llama. También marca la llamada como tail-call, porque ocurre como la última declaración en nuestra función doit
.
¿Duplicado? http://stackoverflow.com/questions/99297/at-as-deep-of-a-level-as-possible-how-are-virtual-functions-implemented – Anonymous
No existe una "clase virtual" en C++. – curiousguy