2009-10-04 9 views
8

Considere el siguiente programa.Confusión en la salida

#include<iostream> 
using namespace std; 

class base 
{ 

    public: 
    int _bval; 

    base():_bval(0){} 
}; 

class derived:public base 
{ 

    public: 
    int _dval; 

    derived():base(),_dval(1){} 
}; 

int main() 
{  

    derived d[5]; 
    base *p; 
    p=d; 
    for(int i=0;i<5;++i,++p) 
     cout<<p->_bval; 

} 

La salida del programa anterior es 01010.
pensé que la salida sería 00.000 porque el valor de _bval se inicializa a 0 (cada vez) por el constructor de la clase base.

¿Pero por qué la salida es diferente de 00000?
¿Qué me estoy perdiendo?

Respuesta

7

Respuesta corta: en C++, las matrices de valores nunca son polimórficas, incluso si su contenido es, y no puede ser tratado de esta manera. Es decir, no puede tratar un derived ad[N] como si fuera un base ab[N].

Respuesta larga: La razón de esto está profundamente enterrada en la aritmética del puntero de C. Si tiene un int* pi y lo incrementa ++pi, no se incrementará simplemente a la siguiente dirección de memoria. Si lo hiciera, no apuntaría al siguiente int ya que esto no comienza en la siguiente dirección. Entonces, en su lugar, se agregan sizeof(int) bytes al puntero. (Un ejemplo concreto podría ayudar: en arquitecturas con 8bit char tipos - char siendo, por definición, lo que C y C++ consideran el tamaño de byte de la arquitectura - y 32bit int tipos, int tiene el tamaño de 4 bytes. Por lo tanto, ++pi agregará 4 a la dirección de punteros, de modo que apunte al siguiente int). La misma aritmética se aplica a todas las demás operaciones del puntero. Así, por ejemplo, con int* pi2=pi+1, pi2 señalarán sizeof(int) bytes detrás pi, aunque pi2-pi rendirá 1.

Así, suponiendo que entiende el último párrafo, vamos a volver a las matrices. Si tiene una matriz derived ad[N], la dirección de ad[1] es sizeof(derived) bytes mayor que la dirección de ad[0].(Eso sin tener en cuenta la alineación para no complicar aún más el problema). Sin embargo, si tiene un base* pb apuntando a ad[0], al aumentarlo, se pondrá el sizeof(base) detrás de la dirección del primer elemento, que si (como es el caso su ejemplo) sizeof(base) < sizeof(derived), es no la dirección de ad[1], pero en algún lugar en el medio de ad[0].

La única cosa que puede hacer para tratar el contenido matriz como si se tratara de todas las clases de base, es iterar sobre la matriz mediante un derived* y lanzar este puntero a base*dentro el bucle:

derived d[5]; 
derived* begin = d; 
const derived* end = d + sizeof(d)/sizeof(d[0]); // points one beyond the last element 
while(begin != end) 
{ 
    base* pb = begin; 
    cout<< pb->_bval; 
    ++begin; 
} 

(Tenga en cuenta que también he cambiado su código para usar los iteradores idiomáticos de inicio/fin de C++)

+0

Gracias por ser preciso. –

11

p[i] le da el valor a sizeof(base) * i bytes después de p. Entonces p[1] no le dará el segundo elemento de d, le dará la segunda mitad del primer elemento.

En otras palabras: si utiliza un puntero a la clase base para iterar sobre una matriz de la clase derivada, obtendrá resultados incorrectos si la clase derivada tiene un tamaño mayor que la clase base porque se repetirá pasos de sizeof(baseclass) bytes.

+0

Tiene razón, si cambiamos 'i <10'suplicaremos' 01' :) – IProblemFactory

+0

¿Pero cómo puede valor de _bval ser '1'? –

+0

'bval' es la única variable miembro de la clase b. Entonces, para obtener el 'bval' de un objeto' base' se toma el valor en 'address_of_the_base_object + 0'. Entonces, cuando p apunta a la segunda mitad de un objeto 'derivado', hará' p + 0' para calcular la dirección de 'bval', que en este caso realmente devolverá la dirección de' dval' porque 'p 'no está realmente apuntando a un objeto' base'. – sepp2k

3

Piensa en el diseño de la memoria de d array.

d-> 0101010101

Cuando cada par de 01 corresponde a un objeto derivado.

Ahora vamos punto P a la misma:

p-> 0101010101

Dado que el tamaño de los objetos de la base es la de una int. Ese segmento de memoria se considera 10 objetos base: el primero con _bval 0, el segundo con _bval 1, ... etc.

+0

¡Aseado! Mostrar el diseño de la memoria y explicar la semántica del puntero base es muy claro. +1 :) – legends2k

1

Además de lo que sepp2k dijo, no inicializó _bval en el constructor de la clase derivada. Debe inicializarlo usando el constructor base.