2012-01-11 24 views
30

Eventuali duplicati:
Can someone explain C++ Virtual Methods?Perché utilizzare le funzioni virtuali?

Ho una domanda per quanto riguarda alle funzioni virtuali del C++.

Perché e quando utilizziamo funzioni virtuali? Qualcuno può darmi un'implementazione in tempo reale o l'uso di funzioni virtuali?

+1

Ho creato un post di blog solo per questo motivo, perché ho trovato difficile spiegarlo quando un bambino mi ha chiesto "perché funzioni virtuali"? http://nrecursions.blogspot.in/2015/06/so-why-do-we-need-virtual-functions.html – Nav

risposta

45

Si utilizzano funzioni virtuali quando si desidera eseguire l'override di un determinato comportamento (metodo di lettura) per la classe derivata anziché quella implementata per la classe base e si desidera farlo in fase di esecuzione tramite un puntatore alla classe di base .

L'esempio classico è quando si ha una classe di base denominata Shape e concrete forme (classi) che ne derivano. Ogni classe concreta esegue l'override (implementa un metodo virtuale) chiamato Draw().

La gerarchia di classi è come segue:

Class hierarchy

Il seguente frammento illustra l'utilizzo dell'esempio; crea una serie di puntatori di classe Shape in cui ognuno punta a un oggetto di classe derivata distinto. In fase di esecuzione, il richiamo del metodo Draw() determina la chiamata del metodo sottoposto a override da quella classe derivata e il particolare Shape viene disegnato (o reso).

Shape *basep[] = { &line_obj, &tri_obj, 
        &rect_obj, &cir_obj}; 
for (i = 0; i < NO_PICTURES; i++) 
    basep[i] -> Draw(); 

Il programma sopra utilizza solo il puntatore alla classe base per memorizzare gli indirizzi degli oggetti classe derivata. Questo fornisce un accoppiamento lasco perché il programma non ha bisogno di cambiare drasticamente se si aggiunge ogni volta che una nuova classe derivata concreta di shape. La ragione è che vi sono segmenti di codice minimi che utilizzano effettivamente (dipendono) sul Shape tipo concreto.

Quanto sopra è un buon esempio del Open Closed Principle dei famosi principi di progettazione SOLID.

+7

Cosa succede se non usiamo 'virtual' e li ridefiniamo in sottoclassi come' Line 'e' Triangolo'. Quale sarebbe la differenza lì? – bluejamesbond

+4

Se la funzione non era virtuale nella classe base, non è possibile accedere alla versione delle classi derivate tramite il puntatore della classe base. – radensb

+0

Siamo spiacenti, ancora un po 'confuso qui. Sarebbe corretto dire che le funzioni virtuali sono per quando non si conosce la versione di una funzione necessaria fino al runtime, ma è necessario anche un istanziamento della classe base qualche tempo prima di allora? Perché sembra che potrei ancora cavarmela senza funzioni virtuali e non sapendo di cosa ho bisogno fino al runtime semplicemente creando sottoclassi e istanziando la classe giusta una volta che lo so. Quindi sembra * che avrai bisogno anche di una istanziazione della classe base qualche tempo prima che * sia critico, giusto? Aka: chiamare la funzione derivata tramite il puntatore di base – krb686

1

Si utilizzerà una funzione virtuale per implementare il "polimorfismo", in particolare dove si dispone di un oggetto, non si conosce l'effettivo tipo sottostante, ma si sa quale operazione si desidera eseguire su di esso e l'implementazione di questo (come lo fa) si differenzia a seconda del tipo che hai effettivamente.

In sostanza quello che viene comunemente chiamato il "principio di sostituzione di liskov", dal nome Barbara Liskov che ha parlato di questo intorno 1983.

Dove è necessario utilizzare le decisioni di esecuzione dinamici in cui, in corrispondenza del punto è chiamato il codice invocando la funzione , non sai quali tipi possono passare attraverso di esso, ora o in futuro, questo è un buon modello da usare.

Tuttavia, non è l'unico modo. Esistono tutti i tipi di "callback" che possono richiedere un "blob" di dati e potresti avere tabelle di callback dipendenti da un blocco di intestazione nei dati che arrivano, ad es. un processore di messaggi. Per questo non c'è bisogno di usare una funzione virtuale, infatti quello che probabilmente usereste è l'ordinamento di come una v-table è implementata solo con una voce (ad esempio una classe con una sola funzione virtuale).

20

Pensate classe di animali, e che ne derivano sono gatto, cane e mucca.Classe animale ha un

virtual void SaySomething() 
{ 
    cout << "Something"; 
} 

funzione.

Invece di stampare "Qualcosa", il cane dovrebbe dire "Abbaio", il gatto dovrebbe dire "Meow". In questo esempio vedi che a is a Dog, ma ci sono delle volte in cui hai un puntatore di animale e non sai quale animale sia. Non vuoi sapere quale animale sia, vuoi solo che l'animale dica qualcosa. Quindi chiami semplicemente funzione virtuale e i gatti diranno "miagolare" e i cani diranno "abbaia".

Ovviamente, la funzione SaySomething avrebbe dovuto essere pura virtuale per evitare possibili errori.

+2

La risposta è stata soddisfacente ... – haris

+0

Non sono del tutto certo a riguardo dell'ultima frase "La funzione SaySomething avrebbe dovuto essere pura virtuale per evitare possibili errori." potresti per favore fare un esempio di questo. – user454083

+3

Ci scusiamo per la risposta tardiva. Considerate questo, uno sviluppatore crea una nuova classe ereditando la nostra classe animale (diciamo, 'Fox') e dimentica di sovrascrivere il metodo SaySomething.Se si utilizza il metodo virtuale che ho fornito, 'casi Fox' dirà "Something", che non è giusto. Se abbiamo dichiarato SaySomething come pure virtuale, non saremmo in grado di istanziare 'Fox', un codice che contiene' nuova Fox (...) 'genera un errore. In questo modo, lo sviluppatore che ha creato la classe 'Fox' verrà informato del suo errore in fase di compilazione. Errori di compilazione del tempo sono buone dal momento che non perdere tempo :) – holgac

20

Si utilizzano le funzioni virtuali quando è necessario gestire oggetti diversi nello stesso modo. Si chiama polimorfismo. Immaginiamo di avere una certa classe di base - qualcosa come forma classica:

class Shape 
    { 
     public: 
      virtual void draw() = 0; 
      virtual ~Shape() {} 
    }; 

    class Rectange: public Shape 
    { 
     public: 
      void draw() { // draw rectangle here } 
    }; 


    class Circle: public Shape 
    { 
     public: 
      void draw() { // draw circle here } 
    }; 

Ora si può avere vettore di diverse forme:

vector<Shape*> shapes; 
    shapes.push_back(new Rectangle()); 
    shapes.push_back(new Circle()); 

E si può trarre tutte le forme in questo modo:

for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++) 
    { 
      (*i)->draw(); 
    } 

In questo modo si disegnano forme diverse con un metodo virtuale: draw(). La versione corretta del metodo viene selezionata in base alle informazioni di runtime sul tipo di oggetto dietro il puntatore.

Avviso Quando si utilizzano le funzioni virtuali si possono dichiarare come puro virtuale (come in Forma di classe, solo luogo "= 0" dopo che il metodo proto). In questo caso non sarai in grado di creare istanze di oggetti con pura funzione virtuale e si chiamerà Classe astratta.

Notare anche "virtuale" prima del distruttore. Nel caso in cui pianifichi il lavoro con gli oggetti tramite puntatori alle loro classi base dovresti dichiarare il distruttore virtuale, quindi quando chiami "cancella" per il puntatore della classe base, verrà chiamata tutta la catena di distruttori e non ci saranno perdite di memoria.

Problemi correlati