2012-04-03 29 views
7

En el siguiente código, PC == pA:encasillamiento con funciones virtuales

class A 
{ 
}; 

class B : public A 
{ 
public: 
    int i; 
}; 

class C : public B 
{ 
public: 
    char c; 
}; 

int main() 
{ 
    C* pC = new C; 
    A* pA = (A*)pC; 

    return 0; 
} 

Pero cuando agrego una función virtual pura a B y ponerlo en práctica en C, pA = pC:

class A 
{ 
}; 

class B : public A 
{ 
public: 
    int i; 
    virtual void Func() = 0; 
}; 

class C : public B 
{ 
public: 
    char c; 
    void Func() {} 
}; 

int main() 
{ 
    C* pC = new C; 
    A* pA = (A*)pC; 

    return 0; 
} 

¿Por qué pA no es igual a pC en este caso? ¿No señalan ambos el mismo objeto "C" en la memoria?

+2

¿Qué quieres decir exactamente con que son iguales? ¿Significa esto que obtienes los valores esperados cuando lanzas de uno a otro en el caso A pero no en el caso B? –

+3

Trate de proporcionar un ejemplo que esté más cerca de su código real, porque su suposición es incorrecta (el error está en otra parte). [Esos apuntadores apuntan a la misma ubicación] (http://ideone.com/oNeFO). –

+0

Puedo estar un poco confundido. Pensé que pA y pC tendrían el mismo valor si apuntan al mismo objeto en la memoria, incluso si uno es un A * y el otro es un C *. http://img831.imageshack.us/img831/6848/screenshotsbc.jpg – user987280

Respuesta

6

Está viendo un valor diferente para su puntero porque la nueva función virtual está causando la inyección de un puntero vtable en su objeto. VC++ pone el puntero vtable al principio del objeto (que es típico, pero puramente un detalle interno).

Agreguemos un nuevo campo a A para que sea más fácil de explicar.

class A { 
public: 
    int a; 
}; 
// other classes unchanged 

Ahora, en la memoria, su pA y A ser algo como esto:

pA --> | a  |   0x0000004 

Una vez que añadir B y C en la mezcla, se termina con esto:

pC --> | vtable |   0x0000000 
pA --> | a  |   0x0000004 
     | i  |   0x0000008 
     | c  |   0x000000C 

Como puede ver, pA apunta a los datos después de el vtable, porque no sabe nada sobre t él vtable o cómo usarlo, o incluso que está allí. pC sabe sobre el vtable, por lo que apunta directamente a la tabla, lo que simplifica su uso.

+0

Solo por curiosidad, ¿cómo funciona la implementación de GCC? Las direcciones son las [mismas] (http://ideone.com/PbZ4z) con GCC. –

+0

@ TamásSzelei, no lo sé con certeza. Es de suponer que GCC pone el vtable para B después de todos los campos de A. O alternativamente, GCC podría reconocer que, dado que A no tiene campos propios, en realidad no importa dónde 'pA no hay necesidad de molestarse en cambiar el puntero cuando se convierte entre A y B (o C). –

+0

Pedí un [seguimiento] (http://stackoverflow.com/questions/10009132/how-does-the-g-implementation-handle-this-situation) desde entonces y obtuve una buena respuesta. Su suposición es correcta, es la optimización de la clase base vacía. Agregar un campo a 'A' hará que los punteros sean diferentes. –

3

Un puntero a un objeto es convertible a un puntero al objeto base y viceversa, pero la conversión no tiene por qué ser trivial. Es completamente posible, y a menudo necesario, que el puntero base tenga un valor diferente que el puntero derivado. Es por eso que tiene un sistema de tipo fuerte y conversiones. Si todos los punteros fueran iguales, tampoco lo necesitaría.

+0

Supongo que es ahí donde está entrando mi confusión. Pensé que los punteros tendrían el mismo valor si apuntaban al mismo objeto en la memoria. Tienen el mismo valor sin la función virtual. Pero cuando se agrega la función virtual, los valores son diferentes. ¿Hay alguna forma de comprobar si pA y pC apuntan al mismo objeto en la memoria si los valores son diferentes? – user987280

+1

@ user987280: ** No ** apuntan al mismo objeto. El puntero derivado apunta al objeto completo más derivado, y el puntero base apunta al subobjeto base. ** A veces ** el subobjeto base tiene la misma dirección que el objeto que contiene la mayoría de los derivados, pero eso es completamente por coincidencia. –

+0

"A veces, el subobjeto base tiene la misma dirección que el objeto que contiene la mayoría de los derivados, pero eso es completamente por coincidencia". Por lo tanto, rayar la función virtual y mirar el primer bit del código que publiqué. El hecho de que pA y pC tengan el mismo valor no es algo en lo que se deba confiar. No lo sabía, eso es para llamar mi atención. Realmente estaba confiando en eso antes del fiasco de la función virtual. – user987280

0

Aquí están mis suposiciones, basadas en la pregunta.

1) Tienes un caso en el que lanzas de una C a una A y obtienes el comportamiento esperado.
2) Agregaste una función virtual, y ese reparto ya no funciona (dado que ya no puedes extraer datos de A directamente después del lanzamiento a A, obtienes datos que no tienen sentido para ti).

Si estas suposiciones son ciertas, la dificultad que experimenta es la inserción de la tabla virtual en B. Esto significa que los datos en la clase ya no se alinean perfectamente con los datos en la clase base (como en la clase bytes agregados, la tabla virtual, que están ocultos para usted). Una prueba divertida sería verificar sizeof para observar el crecimiento de bytes desconocidos.

Para resolver esto, no debe lanzar directamente de A a C a los datos de cosecha. Debería agregar una función getter que está en A y heredada por B y C.

Dada su actualización en los comentarios, creo que debería leer this, explica las tablas virtuales y el diseño de la memoria, y de qué depende el compilador . Ese enlace explica, con más detalle, lo que expliqué anteriormente, pero da ejemplos de que los punteros son valores diferentes. Realmente, tenía POR QUÉ me estabas preguntando mal, pero parece que la información sigue siendo la que querías. El elenco de C a A tiene en cuenta la tabla virtual en este punto (nota C-8 es 4, que en un sistema de 32 bits sería el tamaño de la dirección necesaria para la tabla virtual, creo).

Cuestiones relacionadas