2010-04-23 39 views
5

Supponiamo di avere una classe Dog che eredita da una classe Animal. Qual è la differenza tra queste due righe di codice?Puntatore della classe base contro il puntatore della classe ereditata?

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

In uno, il puntatore è per la classe base e nell'altro il puntatore è per la classe derivata. Ma quando questa distinzione diventerà importante? Per il polimorfismo, uno dei due funzionerebbe esattamente allo stesso modo, giusto?

+1

In questo caso specifico funzionerebbe allo stesso modo. Ma supponiamo che tu abbia un altro gatto di classe che eredita da Animal. Non puoi passare un gatto alla funzione che si aspetta un cane (che facilmente), ma puoi passare un gatto a un animale. Il polimorfismo ha senso "solo" con più di una classe derivata – DaClown

risposta

11

Per tutti gli scopi di tipo controllo, la tratta compilatore a come se si potesse puntare a qualsiasi animale, anche se sai che punta a un cane:

  • non è possibile passare a una funzione a mi aspetto un Dog*.
  • Non è possibile eseguire a->fetchStick(), dove fetchStick è una funzione membro di Dog ma non Animal.
  • Dog *d2 = dynamic_cast<Dog*>(d) è probabilmente solo una copia puntatore sul compilatore. Dog *d3 = dynamic_cast<Dog*>(a) probabilmente non lo è (sto speculando qui, non ho intenzione di preoccuparmi di controllare alcun compilatore.Il punto è: il compilatore probabilmente fa diverse assunzioni su a e d durante la trasformazione del codice).
  • ecc

È possibile richiamare funzioni virtuali (cioè, l'interfaccia definita polimorfo) di Animal ugualmente attraverso una di loro, con lo stesso effetto. Supponendo che Dog non li abbia nascosti comunque (buon punto, JaredPar).

Per le funzioni non virtuali definite in Animale e anche definite (sovraccarico) in Cane, chiamare tale funzione tramite a è diverso dal chiamarlo tramite d.

1

No, non sono la stessa cosa.

Il puntatore del cane non è così polimorfo come animale. Tutto ciò che può indicare in fase di esecuzione è un cane o una sottoclasse di cani. Se non ci sono sottoclassi di Dog, il tipo di runtime Dog e i tipi di tempo di compilazione sono gli stessi.

Il puntatore animale può fare riferimento a qualsiasi sottoclasse di animali: cane, gatto, Wildebeast, ecc

-1

Non fa alcuna differenza reale in fase di esecuzione, come le due istanze sono gli stessi. L'unica differenza è al momento della compilazione, in cui è possibile chiamare ad esempio d-> bark() ma non a-> bark(), anche se in realtà contiene un cane. Il compilatore considera la variabile come un animale e solo quello.

+0

È importante se si va a chiamare una funzione non virtuale dell'oggetto. – tloach

+0

* "Non fa alcuna differenza in fase di esecuzione, ... L'unica differenza è in fase di esecuzione ** –

+0

Sì ... Intendevo il tempo di compilazione mi dispiace:] – Shtong

4

La risposta a questa domanda è un gigante: Dipende

Ci sono molti modi in cui il tipo del puntatore potrebbe diventare importante. Il C++ è un linguaggio molto complesso e uno dei modi in cui viene presentato è l'ereditarietà.

Facciamo un breve esempio per dimostrare uno dei molti modi in cui ciò potrebbe avere importanza.

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; 
} 

Il motivo per cui è una stranezza del modo in cui C++ esegue la ricerca del nome. In breve: quando si cerca un metodo per chiamare C++, verrà eseguita la gerarchia di tipi alla ricerca del primo tipo che ha un metodo con il nome corrispondente.Quindi cercherà un sovraccarico corretto dai metodi con quel nome dichiarato su quel tipo. Poiché Dog dichiara solo un metodo MakeSound senza parametri, nessuna corrispondenza di sovraccarico e non riesce a compilare.

+0

Questo può essere superato attaccando un" usando Animal :: MakeSound (const char *) "nella definizione della classe Dog, credo. –

+0

@ dash-tom-bang sicuramente può ma è solo un esempio di dove il comportamento può differire – JaredPar

+0

Nessun argomento con quello;;) –

0

La differenza è importante quando si tenta di chiamare i metodi di Dog che non sono il metodo di Animal. Nel primo caso (puntatore su Animale) devi prima puntare il puntatore su Cane. Un'altra differenza è se capita di sovraccaricare il metodo non virtuale. Quindi verranno chiamati Animal :: non_virtual_method() (puntatore a Animale) o Dog :: non_virtual_method (puntatore a Cane).

2

La prima linea consentono di chiamare solo i membri della classe Animale su un:

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. 

Anche se (come sottolineato da altri), in questo cas come a è ancora un animale, non è possibile fornire a come parametro di una funzione che chiede per una più specifica classe figlia che è Dog:

void toy(Dog* dog); 

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

la seconda linea consente di utilizzare le funzioni specifiche della classe figlia:

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

Quindi usa la classe base come l'interfaccia "generica" ​​della tua gerarchia di classi (permettendoti di fare mangiare tutti gli animali() senza preoccuparti di come).

2

La distinzione è importante quando si chiama una funzione virtuale utilizzando il puntatore. Diciamo che Animal e Dog hanno entrambi funzioni chiamate do_stuff().

  1. Se Animale :: do_stuff() viene dichiarato virtuale, chiamando do_stuff() su un puntatore animale sarà chiamare Dog :: do_stuff().

  2. Se Animal :: do_stuff() non è dichiarato virtuale, chiamando do_stuff() su un puntatore Animal chiamerà Animal :: do_stuff().

Ecco un programma di lavoro completo per dimostrare:

#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(); 
} 

uscita:

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

Questo è solo un esempio. Le altre risposte elencano altre importanti differenze.

0

Devi sempre ricordare che ci sono 2 parti in ogni classe, i dati e l'interfaccia.

Il codice ha veramente creato 2 oggetti Dog nello heap. Il che significa che i dati sono di cane. Questo oggetto è di dimensioni la somma di tutti i dati membri Dog + Animal + il puntatore vtable.

I ponters ae d (lvalue) differiscono dal punto di vista dell'interfaccia. Che determina come puoi trattarli in base al codice. Quindi, anche se Animal * a è veramente un cane, non è possibile accedere a a-> Bark() anche se Dog :: Bark() esiste. d-> Bark() avrebbe funzionato bene.

Aggiungendo il vtable all'immagine, supponendo che l'interfaccia di Animal abbia Animale :: Muovi un Move generico() e che Cane sovrascriva davvero con un Cane :: Muovi() {come un cane}.

Anche se avevi Animal a * ed esegui a-> Move() grazie al vtable verrai effettivamente Move() {like a dog}. Ciò accade perché Animal :: Move() era un puntatore a funzione (virtuale) puntato su Dog's :: Move() durante la costruzione di Dog().

Problemi correlati