Si estamos familiarizados con el acceso a los elementos de la matriz mediante la aritmética del puntero, es fácil entender cómo funcionan los objetos en la memoria y cómo funciona dynamic_cast
. Considere la siguiente clase simple:
struct point
{
point (int x, int y) : x_ (x), y_ (y) { }
int x_;
int y_;
};
point* p = new point(10, 20);
Supongamos que p
se asigna a la posición de memoria 0x01
. Sus variables miembro se almacenan en sus propias ubicaciones dispares, digamos x_
se almacena en 0x04
y y_
en 0x07
. Es más fácil visualizar el objeto p
como una matriz de punteros. p
(en nuestro caso (0x1
) señala el comienzo de la matriz:
0x01
+-------+-------+
| | |
+---+---+----+--+
| |
| |
0x04 0x07
+-----+ +-----+
| 10 | | 20 |
+-----+ +-----+
Así código para acceder a los campos esencialmente se convertirá en el acceso elementos de la matriz utilizando la aritmética de punteros:
p->x_; // => **p
p->y_; // => *(*(p + 1))
Si el soporte de idiomas Algún tipo de gestión de memoria automática, como GC, se pueden agregar campos adicionales al arreglo de objetos detrás de la escena. Imagine una implementación C++ que recolecta basura con la ayuda del recuento de referencias. Entonces el compilador podría agregar un campo adicional (rc) para mantener el tra ck de ese conteo. La representación de matriz anterior se convierte en:
0x01
+-------+-------+-------+
| | | |
+--+----+---+---+----+--+
| | |
| | |
0x02 0x04 0x07
+--+---+ +-----+ +-----+
| rc | | 10 | | 20 |
+------+ +-----+ +-----+
La primera celda apunta a la dirección del recuento de referencias. El compilador emitirá código apropiado para acceder a las porciones de p
que deben ser visibles para el mundo exterior:
p->x_; // => *(*(p + 1))
p->y_; // => *(*(p + 2))
Ahora es fácil entender cómo dynamic_cast
obras. El compilador trata con clases polimórficas al agregar un puntero oculto adicional a la representación subyacente. Este puntero contiene la dirección del comienzo de otra "matriz" llamada vtable, que a su vez contiene las direcciones de las implementaciones de funciones virtuales en esta clase. Pero la primera entrada de vtable es especial. No apunta a una dirección de función sino a un objeto de una clase llamada type_info
. Este objeto contiene la información del tipo de tiempo de ejecución del objeto y los punteros a type_info
s de sus clases base.Consideremos el siguiente ejemplo:
class Frame
{
public:
virtual void render (Screen* s) = 0;
// ....
};
class Window : public Frame
{
public:
virtual void render (Screen* s)
{
// ...
}
// ....
private:
int x_;
int y_;
int w_;
int h_;
};
Un objeto de Window
tendrá la siguiente distribución de memoria:
window object (w)
+---------+
| &vtable +------------------+
| | |
+----+----+ |
+---------+ vtable | Window type_info Frame type_info
| &x_ | +------------+-----+ +--------------+ +----------------+
+---------+ | &type_info +------+ +----+ |
+---------+ | | | | | |
| &y_ | +------------------+ +--------------+ +----------------+
+---------+ +------------------+
+---------+ | &Window::render()|
+---------+ +------------------+
+---------+
| &h_ |
+---------+
Consideremos ahora qué va a pasar cuando tratamos de echar una Window*
un Frame*
:
Frame* f = dynamic_cast<Frame*> (w);
dynamic_cast
seguirá los enlaces type_info
del vtable de w
, confirma que Frame
está en su lista de clases base y asigne w
a f
. Si no puede encontrar Frame
en la lista, f
se establece en 0
indicando que la fundición falló. El vtable proporciona una forma económica de representar el type_info
de una clase. Esta es una razón por la cual dynamic_cast
funciona solo para clases con funciones virtual
. Restringir dynamic_cast
a tipos polimórficos también tiene sentido desde un punto de vista lógico. Esto es, si un objeto no tiene funciones virtuales, no se puede manipular de forma segura sin conocer su tipo exacto.
El tipo de destino de dynamic_cast
no tiene que ser polimórfico. Esto nos permite envolver un tipo concreto en un tipo polimórfico:
// no virtual functions
class A
{
};
class B
{
public:
virtual void f() = 0;
};
class C : public A, public B
{
virtual void f() { }
};
C* c = new C;
A* a = dynamic_cast<A*>(c); // OK
Siempre he querido una computadora que utilizara el direccionamiento de coma flotante ... –
Todo es solo un montón de bits. (Ve a tomar tus dispositivos de computación de n estados cuánticos en otro lugar ...) –
No puedo pensar por qué ese conocimiento es importante, siempre y cuando me dé lo que quiero y me comporté de una manera conforme. ¿Me estoy perdiendo algo? Un ejemplo es el "puntero al miembro" que se representa de una manera específica de implementación – Chubsdad