2016-05-03 5 views
5

Supponiamo di avere una classe, "Animale" e sottoclassi, "Gatto" e "Cane".Perché dobbiamo dichiarare metodi virtuali come tali

Diciamo che vogliamo consentire a "Gatto" e "Cane" di fare rumore (gatto: "meow" - cane: "woof") quando passiamo i loro oggetti in una funzione intermedia per qualsiasi "Animale".

Perché dobbiamo utilizzare un metodo virtuale per farlo? Non potremmo semplicemente creare Animal-> makeNoise() senza definire un metodo virtuale in "Animal"? Poiché "Gatto" e "Cane" sono entrambi animali, non sarebbe chiaro che "makeNoise()" si riferisce all'Animale che è stato passato alla funzione?

È solo una questione di sintassi o qualcosa di più? Sono abbastanza sicuro che in Java non dobbiamo farlo.

+4

@Antonio, le classi di base virtuali non hanno assolutamente nulla a che fare con i metodi virtuali. È solo uno dei casi in cui C++ fa un ottimo lavoro abusando di una parola chiave per diversi scopi. –

+0

Cosa succede se hai passato un topo che non aveva alcun metodo 'makeNoise()'. –

risposta

10

In Java, tutte le funzioni membro sono virtual di default (ad eccezione static, private, e final quelli).

In C++, tutte le funzioni membro non sono virtual per impostazione predefinita. La creazione delle funzioni virtual aggiunge un sovraccarico - sia di runtime che di dimensioni dell'oggetto - e la filosofia C++ non paga per ciò che non si usa. La maggior parte dei miei oggetti non sono polimorfici, quindi non dovrei pagare per il polimorfismo a meno che non ne abbia bisogno. Poiché è necessario Animal::makeNoise() per essere virtual, è necessario specificarlo esplicitamente.

+0

In Java, una funzione 'final' può essere virtuale se non è' 'finale' nella classe base. Cioè, una classe può sovrascrivere una funzione virtuale e renderla definitiva per impedire ulteriori sostituzioni, ma è ancora virtuale sull'albero gerarchico. –

+0

Inoltre, dovresti probabilmente menzionare che le funzioni non virtuali forniscono non solo un miglioramento delle prestazioni, ma anche un certo grado di sicurezza. Ad esempio, chiamare una funzione non virtuale da un costruttore può essere sicuro, mentre chiamare le funzioni virtuali dai costruttori è generalmente una cattiva idea (anche se non è così male in C++ come in Java). –

+0

In C++ non si paga per ciò che non si usa. In Java paghi tutto. –

0

In java si utilizzano anche metodi virtuali. Migliora l'accoppiamento lento del software.

Ad esempio, è possibile utilizzare una libreria e non sapere quale animale utilizzano internamente. C'è un'implementazione animale che non conosci e puoi usarla perché è un animale. Ottieni l'animale con un metodo library.getAnimal. Ora puoi usare il loro animale senza sapere quale rumore fa, perché devono implementare il metodo makeNoise.

Modifica: Quindi per rispondere alla tua domanda, C++ vuole una dichiarazione esplicita e in java è implicita. quindi sì, è una specie di idiosincrasia specifica della lingua.

+0

In Java non lo fai e non puoi farlo. Non è possibile * dichiarare * una funzione virtuale in quanto tale perché non esiste tale parola chiave in Java. –

+0

quindi non è possibile sovrascrivere un metodo genitore in java? I metodi pubblici Java sono virtuali di default imho – ChampS

+0

Esatto. E poiché sono virtuali di default, non puoi * dichiararli * come virtuali né devi farlo. Leggi attentamente la domanda. –

0

Si potrebbe dire che devi farlo perché è una delle regole della lingua.

C'è un motivo è utile.

Quando si tenta di convalidare il codice che utilizza un animale, il compilatore sa quali funzioni esistono su un animale. È possibile dire se il codice è corretto senza controllare tutte le classi che derivano da animali. Quindi quel codice non ha bisogno di dipendere da tutte quelle classi derivate. Se si ottiene una nuova classe da Animal ma si dimentica di implementare la funzione makeNoise, si tratta di un errore nella nuova classe, non del codice che utilizza la classe di base Animal e il compilatore può indicarvi tale errore. Senza la funzione virtuale dichiarata in Animal non ci sarebbe modo di dire se è il codice chiamante o la nuova classe che è in errore.

Un punto chiave qui è che questi errori verrebbero catturati in fase di compilazione per C++ a causa della sua tipizzazione statica. Altre lingue possono consentire la digitazione dinamica, che può semplificare alcune cose ma, gli errori verranno individuati solo in fase di esecuzione.

0

In Java, tutte le funzioni sono virtuali per impostazione predefinita. In C++ non lo sono, quindi quando si chiama una funzione non virtuale su un puntatore di un determinato tipo, l'implementazione di quel tipo di tale funzione viene invocata con l'indirizzo dell'oggetto come this.

class Animal { 
public: 
    void sound() { std::cout << "splat\n"; } 
    virtual void appearance() { std::cout << "animaly\n"; } 
}; 

class Cat { 
public: 
    void sound() { std::cout << "meow\n"; } 
    virtual void appearance() { std::cout << "furry\n"; } 
}; 

int main() { 
    Animal a; 
    Cat c; 
    Animal* ac = new Cat; 

    a.sound(); // splat 
    a.appearance(); // animaly 

    c.sound(); // meow 
    c.appearance(); // furry 

    ac->sound(); // splat 
    ac->appearance(); // furry 
} 

Ciò si sarebbe verificato quando si voleva scrivere una funzione che generalizzata su "Animal" piuttosto che richiedere una specifica derivata puntatore di classe.

0

C++ è progettato per funzionare con il minimo sovraccarico possibile, confidando che il programmatore effettui la chiamata corretta. In sostanza, "ti dà la pistola e l'opzione di spararti ai piedi", come piace dire spesso a uno dei miei amici. La velocità e la flessibilità sono fondamentali.

Per causare correttamente il comportamento polimorfico vero, C++ richiede che sia specificato. Però! È richiesto solo che sia specificato nella classe base, poiché tutte le classi derivate erediteranno le funzioni dei membri virtuali. Se un membro eredita una funzione membro virtuale, è buona norma inserire "virtuale" nella dichiarazione, ma non obbligatorio.

Gli ADT di solito implementano pure funzioni virtuali per indicare che le classi derivate DEVONO implementare la funzione. Quali:

animal makeNoise() = 0; /*Indicates this function contains no implementation. 
and must be implemented by derived classes in order to function.*/ 

Ancora, non è richiesto classi derivate comprendono 'virtuale' nei propri membri ereditati fintantoché la classe di base include questo.

1

Se si desidera dedurre il tipo di Animale e quindi chiamare make_sound(), è necessario eseguire un dynamic_cast sull'oggetto animale per ciascun figlio di animale. Ciò includerebbe qualsiasi classe che sia direttamente o indirettamente un figlio della classe Animal.

Questo è sia difficile da mantenere e molto doloroso per qualsiasi cambiamento ad es. Aggiungere una nuova classe come un bambino alla classe Animale.

Poiché la filosofia C++ è efficiente, è necessario chiedere al compilatore di fornire il polimorfismo di runtime in quanto costoso. Come lo faresti? Dichiarando la funzione make_sound() come virtuale. Questo crea un vtable (una tabella di puntatori di funzioni) che fa riferimento a un indirizzo di make_sound() che si differenzia in base al tipo dell'oggetto.

Non c'è bisogno di downcast come indiretto gestisce tutto per te. Ciò che potrebbe essere centinaia di righe di codice è solo una singola riga di codice. Questo è il potere di indirezione !!