2011-01-06 16 views
7

http://en.wikipedia.org/wiki/Typeid¿Cómo funciona typeid y cómo almacenan los objetos la información de la clase?

Esto parece ser un misterio para mí: ¿cómo almacena un compilador información sobre el tipo de objeto? Básicamente, una clase vacía, una vez instanciada, no tiene un tamaño cero en la memoria.

+1

Los objetos C++ nunca son de tamaño cero (excepto que el subobjeto de la clase base puede ser). Sin embargo, esto no se debe a 'typeid', porque si creaste una matriz de algo que fuera de tamaño cero, todos los objetos estarían en la misma dirección. Es útil como una propiedad del lenguaje que los distintos objetos tengan cada uno su propia dirección, por lo tanto, no hay objetos de tamaño cero. Esto no tiene demasiada importancia para los subobjetos de la clase base, aunque solo sea porque cada uno está acostumbrado a la idea de que varias clases base del mismo objeto pueden estar en la misma dirección, eso es lo que normalmente sucede en la herencia individual. –

Respuesta

9

Cómo se almacena está definido por la implementación. Hay muchas formas completamente diferentes de hacerlo.

Sin embargo, para los tipos no polimórficos , es necesario almacenar nada de. Para los tipos no polimórficos typeid devuelve información sobre el tipo static de la expresión, es decir, en tiempo de compilación tipo. El tipo siempre se conoce en tiempo de compilación, por lo que no es necesario asociar ninguna información adicional con objetos específicos (al igual que para el sizeof para que funcione realmente no necesita almacenar el tamaño del objeto en ningún lugar). "Un objeto vacío" que mencione en su pregunta sería un objeto de tipo no polimórfico, por lo que no es necesario almacenar nada en él y no hay ningún problema con que tenga un tamaño cero. (Mientras tanto, los objetos polimórficos no son realmente "vacío" y no tener "tamaño cero en la memoria".)

Para los tipos polimórficos typeid en efecto devuelven la información sobre el dinámica tipo de la expresión, es decir, sobre su plazo -tiempo tipo. Para implementar esto, algo debe almacenarse dentro del objeto real en tiempo de ejecución. Como dije antes, los diferentes compiladores lo implementan de manera diferente. En MSVC++, por ejemplo, el puntero de VMT almacenado en cada objeto polimórfico apunta a una estructura de datos que contiene el llamado RTTI - información de tipo de tiempo de ejecución sobre el objeto - además del VMT real.

El hecho de que mencione objetos de tamaño cero en su pregunta probablemente indica que tiene algunas ideas erróneas sobre lo que typeid puede y no puede hacer. Recuerde, nuevamente, typeid es capaz de determinar el tipo real (es decir, dinámico) del objeto para tipos polimórficos solo. Para los tipos no polimórficos typeid no se puede determinar el tipo real del objeto y se revierte a la funcionalidad primitiva del tiempo de compilación.

+0

esa es la razón número 1 por la que las personas se han estado quejando acerca de C++, porque es una pérdida leve de control de la memoria. Mientras estoy brevemente del lado del programador de control anormal, ¿cómo se hace realmente el tipo de comprobación, es un número entero? ¿Dónde puedo ver la implementación real? – jokoon

+0

@gokoon: si su compilador no lo documenta, necesitará realizar ingeniería inversa desde el código generado (por ejemplo, compilar en asm). –

+0

@gokoon: Puede ver la implementación real en compiladores de código abierto/libre de C++, y hay algunos por ahí. No todos los compiladores tienen que hacer las cosas de la misma manera, por lo que si está interesado en un compilador en particular, como Visual C++, tendrá que investigar ese en particular. –

1

Incluso cuando no se usa información de tipo, una clase vacía no tendrá cero bytes, siempre tiene algo, si recuerdo correcto, el estándar lo exige.

Creo que el typeid se implementa de forma similar a un puntero vtable, el objeto tendrá un puntero "oculto" a su typeid.

+2

No es necesario agregar a cada instancia para admitir typeid: solo puede usar una entrada vtable, ya que cada instancia ya apunta a un vtable dinámico de tipo específico. Es por eso que solo puede usar typeid para obtener el tipo dinámico de un objeto si tiene al menos un método virtual. –

+0

Eso es posible también. – bcsanches

0

Hay varias preguntas en su única pregunta.

En objetos C++ es algo que ocupa memoria. Si no ocupa memoria, no es un objeto (aunque el subobjeto de la clase base no puede ocupar espacio). Entonces, un objeto tiene que ocupar al menos 1 byte.

Un compilador no almacena ningún tipo de información a menos que su clase tenga una función virtual. En ese caso, un puntero a tipo de información a menudo se almacena en un desplazamiento negativo en la tabla de funciones virtuales. Tenga en cuenta que el estándar no menciona ninguna tabla virtual o formato de información de tipo, por lo que es puramente un detalle de implementación.

6

Imagínese todas las clases como si tiene este método virtual, pero sólo si ya tiene se crea otro virtual, y un objeto para cada tipo:

extern std::type_info __Example_info; 
struct Example { 
    virtual std::type_info const& __typeid() const { 
    return __Example_info; 
    } 
}; 
// "__" used to create reserved names in this pseudo-implementation 

entonces se puede imaginar cualquier uso de TypeId en un objeto, typeid(obj), se convierte en obj.__typeid(). El uso en punteros se convierte de manera similar en pointer->__typeid(). Excepto para el uso en punteros nulos (que arroja bad_typeid), el caso del puntero es idéntico al caso sin puntero después de la desreferenciación, y no lo mencionaré más. Cuando se aplica directamente sobre un tipo, imagine que el compilador inserta una referencia directamente al objeto requerido: typeid(Example) se convierte en __Example_info.

Si una clase no tiene RTTI (es decir que no tiene virtuals; por ejemplo NoRTTI a continuación), entonces se puede imaginar que con una idéntica método __typeid que es no virtual. Esto permite la misma transformación en llamadas a métodos como la anterior, basándose en el envío virtual o no virtual de esos métodos, según corresponda; también permite que algunas llamadas a métodos virtuales se transformen en despachos no virtuales, como se puede realizar para cualquier método virtual.

struct NoRTTI {}; // a hierarchy can mix RTTI and no-RTTI, just as use of 
        // virtual methods can be in a derived class even if the base 
        // doesn't contain any 
struct A : NoRTTI { virtual ~A(); }; // one virtual required for RTTI 
struct B : A {}; // ~B is virtual through inheritance 

void typeid_with_rtti(A &a, B &b) { 
    typeid(a); typeid(b); 
    A local_a; // no RTTI required: typeid(local_a); 
    B local_b; // no RTTI required: typeid(local_b); 

    A &ref = local_b; 
    // no RTTI required, if the compiler is smart enough: typeid(ref) 
} 

Aquí, typeid debe utilizar RTTI para ambos parámetros (B podría ser una clase base para un tipo más adelante), pero no necesita RTTI para ninguna de las variables locales debido a que el tipo dinámico (o "tipo de ejecución") es absolutamente conocido. Esto coincide, no por casualidad, con la forma en que las llamadas virtuales pueden evitar el despacho virtual.

struct StillNoRTTI : NoRTTI {}; 

void typeid_without_rtti(NoRTTI &obj) { 
    typeid(obj); 
    StillNoRTTI derived; typeid(derived); 
    NoRTTI &ref = derived; typeid(ref); 

    // typeid on types never uses RTTI: 
    typeid(A); typeid(B); typeid(NoRTTI); typeid(StillNoRTTI); 
} 

Aquí, utilizados por ninguno de obj o ref corresponderá a NoRTTI! Esto es cierto a pesar de que el primero puede ser de una clase derivada (obj podría ser realmente una instancia de A o B ) y aunque ref es sin duda de una clase derivada. Todos los otros usos (la última línea de la función) también se resolverán estáticamente.

Tenga en cuenta que en estas funciones de ejemplo, cada typeid utiliza RTTI o no como lo implica el nombre de la función. (De ahí los usos comentados en with_rtti.)

+0

Tendré que leer eso más de una vez. No configuro esto como la respuesta aceptada, ya que parece bastante detallada, pero la respuesta a continuación es bastante más explicada que el uso de ejemplos brutales. – jokoon

+0

@gokoon: Ah, lo tenías como la respuesta aceptada por un momento ...:) Encuentro más efectivo entretejer la explicación con ejemplos prácticos, especialmente cuando puedes probarlos tú mismo (por ejemplo, aplicando la conversión typeid (obj) a obj .__ typeid() manualmente), pero con diferentes trazos. (Acabo de actualizar tratando de centrarme en la claridad.) –

Cuestiones relacionadas