Las clases están (más o menos) construidas como estructuras normales. Los métodos son (más o menos ...) convertidos en funciones cuyo primer parámetro es "this". Las referencias a las variables de clase se realizan como un desplazamiento a "esto".
En cuanto a la herencia, vamos a citar las preguntas frecuentes de C++ LITE, que se refleja aquí http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4. En este capítulo se muestra cómo las funciones virtuales son llamados en el hardware real (lo que hace la compilación en código máquina que
Vamos a trabajar un ejemplo de clase Base Supongamos que tiene 5 funciones virtuales:.. virt0()
través virt4()
.
// Your original C++ source code
class Base {
public:
virtual arbitrary_return_type virt0(...arbitrary params...);
virtual arbitrary_return_type virt1(...arbitrary params...);
virtual arbitrary_return_type virt2(...arbitrary params...);
virtual arbitrary_return_type virt3(...arbitrary params...);
virtual arbitrary_return_type virt4(...arbitrary params...);
...
};
Paso # 1: el compilador genera una tabla estática que contiene 5 funciones triples, enterrando esa tabla en la memoria estática en alguna parte. Muchos compiladores (no todos) definen esta tabla al compilar el .cpp que define la primera función virtual no en línea de Base. Llamamos a esa tabla la tabla v; imaginemos que su nombre técnico es Base::__vtable
. Si un puntero de función se ajusta en una palabra de máquina en la plataforma de hardware de destino, Base::__vtable
terminará consumiendo 5 palabras ocultas de memoria. No 5 por instancia, no 5 por función; Sólo 5. podría ser algo como lo siguiente pseudo-código:
// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Base::__vtable[5] = {
&Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4
};
Paso # 2: el compilador añade un puntero oculto (por lo general también una máquina de palabra) a cada objeto de la base de clase. Esto se llama v-pointer. Piense en este puntero oculto como un miembro de datos oculta, como si el compilador reescribe su clase a algo como esto:
// Your original C++ source code
class Base {
public:
...
FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer
...
};
Paso # 3: el compilador inicializa this->__vptr
dentro de cada constructor. La idea es causar v Un triple de cada objeto para señalar en la tabla v de su clase, como si se añade la siguiente instrucción en init-la lista de cada constructor:
Base::Base(...arbitrary params...)
: __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer
...
{
...
}
Ahora vamos a trabajar a cabo una clase derivada. Supongamos que su código C++ define la clase Der que hereda de la clase Base. El compilador repite los pasos 1 y 3 (pero no el 2). En el paso # 1, el compilador crea una tabla v oculta, manteniendo los mismos punteros de función que en Base::__vtable
pero reemplazando las ranuras que corresponden a las anulaciones. Por ejemplo, si Der anula virt0()
través virt2()
y hereda los demás tal y como son, v-mesa de Der podría ser algo como (se simula Der no añade ninguna nueva virtuals):
// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp
// Pretend FunctionPtr is a generic pointer to a generic member function
// (Remember: this is pseudo-code, not C++ code)
FunctionPtr Der::__vtable[5] = {
&Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4
}; ^^^^----------^^^^---inherited as-is
En el paso # 3, el compilador agrega una asignación de puntero similar al comienzo de cada uno de los constructores de Der. La idea es cambiar el v-puntero de cada objeto Der para que apunte a la tabla v de su clase. (Este no es un segundo v-puntero, es el mismo v-puntero que se definió en la clase base, Base; recuerde, el compilador no repite el paso 2 en la clase Der).
Finalmente, veamos cómo el compilador implementa una llamada a una función virtual. Su código podría tener este aspecto:
// Your original C++ code
void mycode(Base* p)
{
p->virt3();
}
El compilador tiene ni idea de si esto va a llamar o Base::virt3()
Der::virt3()
o tal vez el método de otra clase derivada que ni siquiera existe todavía virt3()
. Solo se sabe con certeza que está llamando al virt3()
, que es la función en la ranura n. ° 3 de la tabla v. Se vuelve a escribir esa llamada en algo como esto:
// Pseudo-code that the compiler generates from your C++
void mycode(Base* p)
{
p->__vptr[3](p);
}
os recomiendo que todos los desarrolladores C++ para leer las preguntas frecuentes. Puede tomar varias semanas (ya que es difícil de leer y de largo) pero le enseñará mucho sobre C++ y lo que se puede hacer con él.
Se verá más o menos lo mismo que una estructura C. –
¡Derecho !. Así que esto me llevó a pensar cómo se compilarían las estructuras, y me di cuenta de que tal vez no entiendo esta parte, para empezar. bash $ cat struct.cpp struct test { int i; flotador f; }; ¿Qué archivo de objeto y objeto deberá corresponder a este archivo contendrá? Entiendo que los archivos de objetos correspondientes a estructuras no se parecerán a las funciones en el sentido de que no hay instrucciones de ensamblaje. Gracias, – xyz
en particular, ¿esta struct.cpp no debería tener ni un texto ni una sección de datos en los archivos objeto? No sé si esta pregunta se inclina más hacia el formato de los archivos elf. – xyz