2010-04-23 15 views
5

Supongamos que tengo una clase Dog que hereda de una clase Animal. ¿Cuál es la diferencia entre estas dos líneas de código?Puntero de clase base frente a puntero de clase heredado?

Animal *a = new Dog(); 
    Dog *d = new Dog(); 

En uno, el puntero es para la clase base, y en el otro, el puntero es para la clase derivada. ¿Pero cuándo se haría importante esta distinción? Para el polimorfismo, cualquiera de los dos funcionaría exactamente igual, ¿verdad?

+1

Para este caso específico, funcionaría igual. Pero supongamos que tienes otra clase Cat que hereda de Animal. No puedes pasar un gato a la función que espera un perro (así de fácil), pero puedes pasar un gato a un animal. El polimorfismo tiene sentido "solo" con más de una clase derivada – DaClown

Respuesta

11

Para todos los efectos de la comprobación de tipos, los a compilador trata como si pudiera apuntar a cualquier animal, a pesar de saber que apunta a un perro:

  • no se puede pasar a una función a esperando un Dog*.
  • No puede hacer a->fetchStick(), donde fetchStick es una función miembro de Dog pero no Animal.
  • Dog *d2 = dynamic_cast<Dog*>(d) es probablemente solo una copia de puntero en su compilador. Dog *d3 = dynamic_cast<Dog*>(a) probablemente no (estoy especulando aquí, no voy a molestarme en verificar ningún compilador. El punto es: el compilador probablemente hace suposiciones diferentes acerca de a y d al transformar el código).
  • etc.

Puede llamar funciones virtuales (es decir, la interfaz definida polimórfica) de Animal igualmente a través de cualquiera de ellos, con el mismo efecto. Asumiendo Dog no los ha ocultado, de todos modos (buen punto, JaredPar).

Para funciones no virtuales que están definidas en Animal, y también definidas (sobrecargadas) en Dog, llamar a esa función a través de a es diferente de llamarlo a través de d.

1

No, no son lo mismo.

El puntero de Perro no es tan polimórfico como Animal. Todo lo que puede señalar en tiempo de ejecución es un perro o una subclase de perro. Si no hay subclases de Perro, entonces el tipo de tiempo de ejecución de Perro y los tipos de tiempo de compilación son los mismos.

El puntero del animal puede referirse a cualquier subclase de Animal: perro, gato, Wildebeast, etc.

-1

No hace ninguna diferencia real en tiempo de ejecución, ya que los dos casos son los mismos. La única diferencia es en tiempo de compilación, donde puede llamar, por ejemplo, d-> bark() pero no a-> bark(), incluso si realmente contiene un perro. El compilador considera que la variable es un animal y solo eso.

+0

Importa si va a invocar una función no virtual del objeto. – tloach

+0

* "No hay diferencia real en el tiempo de ejecución, ... La única diferencia es en tiempo de ejecución ** –

+0

Sí ... Quise decir tiempo de compilación lo siento:] – Shtong

4

La respuesta a esta pregunta es un gigante: Depende

Hay muchas maneras en las que el tipo del puntero podría llegar a ser importantes. C++ es un lenguaje muy complejo y una de las formas en que aparece es con la herencia.

Tomemos un breve ejemplo para demostrar una de las muchas maneras en que esto podría importar.

class Animal { 
public: 
    virtual void MakeSound(const char* pNoise) { ... } 
    virtual void MakeSound() { ... } 
}; 

class Dog : public Animal { 
public: 
    virtual void MakeSound() {... } 
}; 

int main() { 
    Animal* a = new Dog(); 
    Dog* d = new Dog(); 
    a->MakeSound("bark"); 
    d->MakeSound("bark"); // Does not compile 
    return 0; 
} 

La razón por la cual es una peculiaridad de la forma en que C++ busca los nombres. En resumen: cuando se busca un método para llamar a C++, se recorre la jerarquía de tipos buscando el primer tipo que tiene un método del nombre coincidente.Luego buscará una sobrecarga correcta de los métodos con ese nombre declarado en ese tipo. Dado que Dog solo declara un método MakeSound sin parámetros, ninguna sobrecarga coincide y no se puede compilar.

+0

Esto se puede solucionar pegando un" using Animal :: MakeSound (const char *) "en la definición de la clase Dog, creo. –

+0

@ dash-tom-bang definitivamente puede, pero es solo un ejemplo de donde el comportamiento puede diferir – JaredPar

+0

No hay argumentos con eso.;) –

0

La diferencia es importante cuando intentas llamar a los métodos de Dog que no son el método de Animal. En el primer caso (puntero a Animal), primero debes lanzar el puntero a Perro. Otra diferencia es si sobrecarga el método no virtual. A continuación, se llamará Animal :: non_virtual_method() (puntero a Animal) o Dog :: non_virtual_method (puntero a Dog).

2

La primera línea permite llamar sólo a los miembros de la clase de animal en una:

Animal *a = new Dog(); 
a->eat(); // assuming all Animal can eat(), here we will call Dog::eat() implementation. 
a->bark(); // COMPILATION ERROR : bark() is not a member of Animal! Even if it's available in Dog, here we manipulate an Animal. 

Aunque (como se ha señalado por otros), en este cas como a sigue siendo un animal, no puede proporcionar a como un parámetro de una función pidiendo una clase hija que es más específica del perro:

void toy(Dog* dog); 

toy(a); // COMPILATION ERROR : we want a Dog! 

la segunda línea le permite utilizar las funciones específicas de la clase hija:

Dog *a = new Dog(); 
a->bark(); // works, but only because we're manipulating a Dog 

Utilice la clase base como la interfaz "genérica" ​​de su jerarquía de clases (lo que le permite hacer que todos sus animales coman() sin preocuparse por cómo).

2

La distinción es importante cuando llama a una función virtual utilizando el puntero. Digamos que Animal y Perro tienen funciones llamadas do_stuff().

  1. Si Animal :: do_stuff() se declara virtual, llamando do_stuff() en un puntero animal se llame perro :: do_stuff().

  2. Si Animal :: do_stuff() no se declara virtual, al llamar do_stuff() en un puntero Animal se llamará Animal :: do_stuff().

He aquí un programa de trabajo completo para demostrar:

#include <iostream> 

class Animal { 
public: 
     void do_stuff() { std::cout << "Animal::do_stuff\n"; } 
     virtual void virt_stuff() { std::cout << "Animal::virt_stuff\n"; } 
}; 

class Dog : public Animal { 
public: 
     void do_stuff() { std::cout << "Dog::do_stuff\n"; } 
     void virt_stuff() { std::cout << "Dog::virt_stuff\n"; } 
}; 

int main(int argc, char *argv[]) 
{ 
     Animal *a = new Dog(); 
     Dog *b = new Dog(); 

     a->do_stuff(); 
     b->do_stuff(); 
     a->virt_stuff(); 
     b->virt_stuff(); 
} 

Salida:

Animal::do_stuff 
Dog::do_stuff 
Dog::virt_stuff 
Dog::virt_stuff 

Este es sólo un ejemplo. Las otras respuestas enumeran otras diferencias importantes.

0

Siempre debe recordar que hay 2 partes en cada clase, los datos y la interfaz.

Su código realmente creó 2 objetos para perros en el montón. Lo que significa que los datos son de Dog. Este objeto tiene el tamaño de la suma de todos los datos de Perro + Animal + el puntero vtable.

Los ponters a y d (lvalues) difieren desde el punto de vista de la interfaz. Lo que determina cómo puedes tratarlos con respecto al código. Entonces, aunque Animal * a es realmente un perro, no se puede acceder a-> Bark() incluso si existiera Dog :: Bark(). d-> Bark() habría funcionado bien.

Añadiendo el vtable de nuevo a la imagen, asumiendo que la interfaz de Animal had Animal :: Move a Move genérica() y que Dog realmente sobrescriba con un Dog :: Move() {like a dog}.

Incluso si tuvieras Animal a * y ejecutaras a-> Move() gracias a vtable, en realidad te moverías() {like a dog}. Esto sucede porque Animal :: Move() era un puntero de función (virtual) redirigido a Dog's :: Move() al construir Dog().

Cuestiones relacionadas