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.)
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. –