2011-07-07 10 views
5

estoy leyendo un texto C++ y tengo el siguiente código:Pregunta sobre la conformidad de los tipos en C++?

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

void main() { 
    A* p1 = new B; // B may be larger than A :OK [Line 1] 
    B* p2 = new A; // B may be larger than A :Not OK [Line 2] 
} 

tengo 2 preguntas:

  1. No entiendo lo que quiere decir el autor comentando en las líneas 1 y 2
  2. ¿Por qué no podemos hacer en la línea 2?
+1

De hecho, creo que 'A' puede ser más grande que' B', debido a la optimización de la clase base vacía y algunos diseños ABI extraños. –

Respuesta

8

Bueno, "más grande" no es la clave aquí. El problema real es la relación "es una".

Cualquier objeto de class B es también de tipo class A (class B es también class A debido a la herencia), por lo que la primera línea está bien (el puntero a class A puede igual de bien apuntar a un objeto de class B), pero el inverso es no es verdadero (class A no es class B y puede que incluso no tenga idea de la existencia de class B), por lo que la segunda línea no se compilará.

0

Debido B se deriva de A, el indicador B * no puede apuntar a A.

0

B* p2 = new A; es válida porque un B puntero a puede esperar B para tener más información que A.

Ejemplo:

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

B* p2 = new A; 
p2->notInA = 5; // Wait, which notInA are we talking about? 
       // p2 is really an A, and As don't have notInA! 
7

El autor se está demostrando que él no entiende C++ (o la programación en general). No hay problema de tamaño ("más grande") involucrado. El problema es que B "isA" A, por lo que un puntero a A se puede inicializar con un puntero a B. Pero lo contrario no es verdad.

+0

Hay tantos libros malos de C++ que lloro para principiantes que no saben nada. –

1

Los comentarios son tontos, de verdad. El tamaño del objeto no tiene mucho que ver con eso. El problema es que puedes subir los tipos de puntero implícitamente, pero no abatir.

BTW, maindeben tienen un tipo de devolución de int. No void.

0

Cuando está utilizando un puntero, el valor del puntero es una ubicación de memoria. Esto significa que puede establecer un puntero para señalar CUALQUIER punto en la memoria. Para "desreferenciar" esa memoria y trabajar con el objeto almacenado en ese punto, necesita saber qué tipo de objeto para esperar.
Cuando la clase B hereda la clase A, B abarcará una "A". Sharptooth está en lo cierto al decir que hay una relación "Es-A". "B" es una "A", pero "A" no es una "B".
El problema sería exactamente el mismo en el siguiente código:

string* s = new string(""); 
int a = 44; 
s = (string*)&a; //compiler error if not cast 
cout << s; // randomness printed. 
0
class B : public A { }; 

A* p1 = new B; // B may be larger than A :OK [Line 1] 
B* p2 = new A; // B may be larger than A :Not OK [Line 2] 

No entiendo lo que quiere decir el autor comentando en las líneas 1 y 2.
Por qué no podemos hacer en la línea 2?

class B se deriva de class A, que - desde la perspectiva de las variables miembro que contiene - significa que tiene todo lo que una tiene y todo lo que opta por sumarse. En su código simple, B no ha agregado nada, pero si tuviera miembros adicionales de datos, claramente requeriría más memoria para almacenar que el tipo más simple A. Si agrega una función de miembro virtual donde A no tenía ninguno, se podría esperar que el compilador agregue un puntero en B que registre la dirección de la tabla de despacho virtual que enumera las direcciones de sus funciones de miembro virtual. El compilador también puede agregar relleno si así lo desea.

En consecuencia, el caso general es que el tamaño de una clase derivada es >= del tamaño de su clase base.

A* p1 = new B; // B may be larger than A :OK [Line 1] 

Aquí, sin embargo la cantidad de espacio necesario B realidad está siendo asignado del montón/libre de la tienda, y la dirección de esa memoria almacenada en p1. Si B es más grande que A, no hace ninguna diferencia, eso está en otro lugar de todos modos, la clave es que un B* está garantizado para poder almacenarse en un A*.

B* p2 = new A; // B may be larger than A :Not OK [Line 2] 

Aquí, una nueva A se está creando en el montón, pero el programador está tratando de decirle al compilador que hay un B en esa dirección. El compilador no lo creerá (a menos que sea forzado); simplemente obtendrá un error de tiempo del compilador. Si lo hace obligar al compilador (por ejemplo, p2 = (B *) (nueva A) ) to treat the memory address in p2 as if it were an B , then it may later try to access additional data it expects to be part of any B which simply doesn't exist in any A`: Miembros de datos adicionales, punteros de despacho virtuales, etc ..

1

Aquí todo el mundo está dando respuestas correctas, pero me gustaría señalar lo que quiere decir el autor con "grande", etc.
Considere estas dos clases:

class Animal { 
    public: 
    bool bIsHungry; 
}; 

class Bird : public Animal { 
    public: 
    bool bIsFlying; 
} 

Entonces cuando llamo

Animal* animal = new Bird; // B may be larger than A :OK [Line 1] 

el programa asigna suficiente espacio para ajustar la variable "bIsHungry" y la variable "bIsFlying". (Sin embargo, a menos que encasillado "animal", sólo se podrá acceder a la "bIsHungry" a pesar de que "bIsFlying" también está reservado para los "animales" en la memoria.)

Cuando se llama a

Bird* parrot = new Animal; // B may be larger than A :Not OK [Line 2] 

el programa solo asigna suficiente espacio para ajustarse a la variable "bIsHungry". Sin embargo, el usuario de "loro" podría querer escribir código como

if(parrot->bIsFlying) 
{ //doSomething() 
    ... 
} 

Esto no funcionará, ya que con "nuevo animal", el programa sólo espacio asignado para la clase de animal, es decir, "bIsHungry" y había no hay memoria asignada para "bIsFlying". El compilador ya "ve" eso y se "quejará", es decir, informará un error.

+0

Muchas gracias por un claro ejemplo. – ipkiss