Spiegazione virtuale / pura virtuale


346

Cosa significa esattamente se una funzione è definita come virtuale ed è la stessa di virtuale puro?

Risposte:


339

Dalla funzione virtuale di Wikipedia ...

Nella programmazione orientata agli oggetti, in linguaggi come C ++ e Object Pascal, una funzione o metodo virtuale è una funzione o metodo ereditabile e sostituibile per cui è facilitato l'invio dinamico. Questo concetto è una parte importante della parte del polimorfismo (runtime) della programmazione orientata agli oggetti (OOP). In breve, una funzione virtuale definisce una funzione di destinazione da eseguire, ma la destinazione potrebbe non essere nota al momento della compilazione.

A differenza di una funzione non virtuale, quando una funzione virtuale viene ignorata, la versione più derivata viene utilizzata a tutti i livelli della gerarchia di classi, anziché solo al livello in cui è stata creata. Pertanto, se un metodo della classe base chiama un metodo virtuale, verrà utilizzata la versione definita nella classe derivata anziché la versione definita nella classe base.

Ciò è in contrasto con le funzioni non virtuali, che possono ancora essere ignorate in una classe derivata, ma la "nuova" versione verrà utilizzata solo dalla classe derivata e inferiore, ma non cambierà affatto la funzionalità della classe base.

mentre..

Una funzione virtuale pura o metodo virtuale puro è una funzione virtuale che deve essere implementata da una classe derivata se la classe derivata non è astratta.

Quando esiste un metodo virtuale puro, la classe è "astratta" e non può essere istanziata da sola. Invece, deve essere utilizzata una classe derivata che implementa i metodi pure-virtual. Un puro-virtuale non è affatto definito nella classe base, quindi una classe derivata deve definirlo, o anche quella classe derivata è astratta e non può essere istanziata. Solo una classe che non ha metodi astratti può essere istanziata.

Un virtuale fornisce un modo per sovrascrivere la funzionalità della classe base e un puro-virtuale lo richiede .


10
Quindi ... è puro virtuale una parola chiave o solo un termine che viene utilizzato?
Justin,

197
Funzione void virtuale () = 0; è un puro virtuale. "= 0" indica la purezza.
Goz,

8
Justin, "puro virtuale" è solo un termine (non una parola chiave, vedi la mia risposta di seguito) usato per indicare "questa funzione non può essere implementata dalla classe base. Come ha detto Goz, aggiungendo" = 0 "alla fine di un virtuale la funzione lo rende "puro"
Nick Haddad,

14
Credo che Stroustrup abbia detto che voleva aggiungere una pureparola chiave, ma che Bell Labs stava per rilasciare un'importante versione di C ++ e che il suo manager non glielo avrebbe permesso in quella fase avanzata. L'aggiunta di parole chiave è un grosso problema.
quark,

14
Questa non è una buona risposta Qualsiasi metodo può essere ignorato, non solo quelli virtuali. Vedi la mia risposta per maggiori dettagli.
Asik,

212

Vorrei commentare la definizione di virtual di Wikipedia, come ripetuto da molti qui. [Al momento della stesura di questa risposta,] Wikipedia ha definito un metodo virtuale come uno che può essere ignorato in sottoclassi. [Fortunatamente, Wikipedia è stata modificata da allora e ora lo spiega correttamente.] Ciò è errato: qualsiasi metodo, non solo quelli virtuali, può essere ignorato in sottoclassi. Ciò che fa virtuale è darti polimorfismo, cioè la possibilità di selezionare in fase di esecuzione l'override più derivato di un metodo .

Considera il seguente codice:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

Qual è l'output di questo programma?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Derivata ignora ogni metodo di Base: non solo quello virtuale, ma anche quello non virtuale.

Vediamo che quando hai un Base-pointer-to-Derived (bDerived), chiamare NonVirtual chiama l'implementazione della classe Base. Questo viene risolto in fase di compilazione: il compilatore vede che bDerived è una Base *, che NonVirtual non è virtuale, quindi esegue la risoluzione sulla classe Base.

Tuttavia, la chiamata a Virtual chiama l'implementazione della classe Derived. A causa della parola chiave virtuale, la selezione del metodo avviene in fase di esecuzione , non in fase di compilazione. Quello che succede qui al momento della compilazione è che il compilatore vede che si tratta di una Base * e che sta chiamando un metodo virtuale, quindi inserisce una chiamata alla vtable anziché alla classe Base. Questa vtable viene istanziata in fase di runtime, quindi la risoluzione di runtime alla sostituzione più derivata.

Spero che non sia stato troppo confuso. In breve, qualsiasi metodo può essere sovrascritto, ma solo i metodi virtuali offrono polimorfismo, ovvero la selezione runtime della sostituzione più derivata. In pratica, tuttavia, l'override di un metodo non virtuale è considerato una cattiva pratica e raramente utilizzato, quindi molte persone (incluso chiunque abbia scritto l'articolo di Wikipedia) pensano che solo i metodi virtuali possano essere ignorati.


6
Solo perché l'articolo di Wikipedia (che non difendo in alcun modo) definisce un metodo virtuale "come uno che può essere ignorato in sottoclassi" non esclude la possibilità che altri metodi non virtuali con lo stesso nome possano essere dichiarati. Questo è noto come sovraccarico.

26
La definizione è tuttavia errata. Un metodo che può essere sovrascritto in una classe derivata non è virtuale per definizione; se il metodo può essere ignorato è irrilevante per la definizione di "virtuale". Inoltre, "sovraccarico" di solito si riferisce ad avere più metodi con lo stesso nome e tipo di ritorno ma argomenti diversi, nella stessa classe; è molto diverso dalla "sostituzione" che implica esattamente la stessa firma ma in una classe derivata. Quando è fatto non polimorficamente (base non virtuale), viene spesso chiamato "nascondersi".
Asik,

5
Questa dovrebbe essere la risposta accettata. Quel particolare articolo di Wikipedia che mi prenderò il tempo di collegare qui poiché nessun altro su questa domanda lo ha fatto , è spazzatura completa. +1, buon signore.
josaphatv,

2
ORA ha senso. Grazie, buon signore, per aver spiegato correttamente che qualsiasi metodo può essere sostituito da classi derivate e il cambiamento sta nel modo in cui il compilatore si comporterà per scegliere quale funzione viene chiamata in diverse situazioni.
Doodad,

3
Potrebbe essere utile aggiungere un Derived*con le stesse chiamate di funzione per portare il punto a casa. Altrimenti ottima risposta
Jeff Jones,

114

La parola chiave virtuale dà al C ++ la sua 'capacità di supportare il polimorfismo. Quando hai un puntatore a un oggetto di qualche classe come:

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

In questo esempio (sciocco), la funzione GetNumberOfLegs () restituisce il numero appropriato in base alla classe dell'oggetto per cui è richiesto.

Ora, considera la funzione 'SomeFunction'. Non importa quale tipo di oggetto animale gli sia passato, purché sia ​​derivato da Animale. Il compilatore eseguirà automaticamente il cast di qualsiasi classe derivata da animali su un animale in quanto è una classe base.

Se lo facciamo:

Duck d;
SomeFunction(&d);

avrebbe prodotto '2'. Se lo facciamo:

Horse h;
SomeFunction(&h);

avrebbe prodotto '4'. Non possiamo farlo:

Animal a;
SomeFunction(&a);

perché non verrà compilato perché la funzione virtuale GetNumberOfLegs () è pura, il che significa che deve essere implementata derivando le classi (sottoclassi).

Le funzioni virtuali pure vengono utilizzate principalmente per definire:

a) classi astratte

Queste sono classi base in cui devi derivarne e quindi implementare le funzioni virtuali pure.

b) interfacce

Queste sono classi 'vuote' in cui tutte le funzioni sono pure virtuali e quindi devi derivare e quindi implementare tutte le funzioni.


Nel tuo esempio, non puoi fare il n. 4 perché non hai fornito un'implementazione del metodo virtuale puro. Non è strettamente perché il metodo è puro virtuale.
iheanyi,

@iheanyi Non è possibile fornire l'implementazione a un metodo virtuale puro nella classe base. Quindi il caso n. 4 è ancora errore.
prasad,

32

In una classe C ++, virtuale è la parola chiave che indica che un metodo può essere sovrascritto (ovvero implementato da) una sottoclasse. Per esempio:

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

In questo caso una sottoclasse può sovrascrivere la funzione initShape per eseguire alcune operazioni specializzate:

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

Il termine puro virtuale si riferisce a funzioni virtuali che devono essere implementate da una sottoclasse e non sono state implementate dalla classe base. Si definisce un metodo come puro virtuale usando la parola chiave virtuale e aggiungendo un = 0 alla fine della dichiarazione del metodo.

Quindi, se volessi rendere Shape :: initShape puro virtuale, dovrai fare quanto segue:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

Aggiungendo un metodo virtuale puro alla tua classe, rendi la classe una classe base astratta che è molto utile per separare le interfacce dall'implementazione.


1
Riguardo alle "funzioni virtuali che devono essere implementate da una sottoclasse" - questo non è strettamente vero, ma la sottoclasse è anche astratta se non lo sono. E le classi astratte non possono essere istanziate. Inoltre, "non può essere implementato dalla classe base" sembra fuorviante; Suggerirei che "non sono stato" sarebbe meglio poiché non vi sono restrizioni alle modifiche del codice per aggiungere un'implementazione all'interno della classe base.
NVRAM

2
E "la funzione getName non può essere implementata da una sottoclasse" non è del tutto corretta. Le sottoclassi possono implementare il metodo (con la stessa o diversa firma) ma l'implementazione non sostituirà il metodo. È possibile implementare Circle come sottoclasse e implementare "std :: string Circle :: getName ()" - quindi è possibile chiamare uno dei due metodi per un'istanza Circle. Ma se utilizzato attraverso un puntatore o riferimento Shape, il compilatore chiamerebbe Shape :: getName ().
NVRAM

1
Buoni punti su entrambi i fronti. Stavo cercando di evitare di discutere casi speciali per questo esempio, modificherò la risposta per essere più indulgente. Grazie!
Nick Haddad,

@NickHaddad Vecchio thread, ma mi chiedo perché hai chiamato la tua variabile m_name. Che cosa m_significa?
Tqn

1
@Tqn supponendo che NickHaddad abbia seguito le convenzioni, m_name è una convenzione di denominazione comunemente chiamata notazione ungherese. La m indica il membro di una struttura / classe, intero.
Ketcomp,

16

"Virtuale" significa che il metodo può essere sovrascritto in sottoclassi, ma ha un'implementazione richiamabile direttamente nella classe base. "Pure virtual" significa che è un metodo virtuale senza implementazione richiamabile direttamente. Tale metodo deve essere sostituito almeno una volta nella gerarchia dell'ereditarietà - se una classe ha metodi virtuali non implementati, gli oggetti di quella classe non possono essere costruiti e la compilazione fallirà.

@quark sottolinea che i metodi pure-virtual possono avere un'implementazione, ma poiché i metodi pure-virtual devono essere sovrascritti, l'implementazione predefinita non può essere chiamata direttamente. Ecco un esempio di metodo puro-virtuale con un valore predefinito:

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

Secondo i commenti, la mancata compilazione della compilazione è specifica del compilatore. Almeno in GCC 4.3.3, non verrà compilato:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

Produzione:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()

deve essere sovrascritto se si desidera creare un'istanza di un'istanza della classe. Se non crei alcuna istanza, il codice verrà compilato correttamente.
Glen,

1
la compilazione non fallirà. Se non esiste l'implementazione di un metodo (puro) virtuale, allora quella classe / oggetto non può essere istanziato. Potrebbe non essere LINK, ma verrà compilato.
Tim

@Glen, @tim: su quale compilatore? Quando provo a compilare un programma che crea una classe astratta, non viene compilato.
John Millikin,

La compilazione di @John fallirà solo se provi a creare un'istanza di un'istanza di una classe che contiene un PVF. Naturalmente è possibile creare un'istanza del puntatore o dei valori di riferimento per tali classi.

5
Inoltre, John, non è del tutto giusto: "'Virtual puro' significa che è un metodo virtuale senza implementazione." I metodi virtuali puri possono avere implementazioni. Ma non puoi chiamarli direttamente: devi sovrascrivere e utilizzare l'implementazione della classe base all'interno della sottoclasse. Ciò consente di fornire una parte predefinita dell'implementazione. Non è una tecnica comune però.
quark,

9

Come funziona la parola chiave virtuale?

Supponiamo che l'uomo sia una classe base, l'indiano è derivato dall'uomo.

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

Dichiarare do_work () come virtuale significa semplicemente: quale do_work () chiamare sarà determinato SOLO in fase di esecuzione.

Supponiamo che lo faccia

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

Se non viene utilizzato virtuale, lo stesso viene determinato staticamente o associato staticamente dal compilatore, a seconda di quale oggetto sta chiamando. Quindi se un oggetto di Man chiama do_work (), Man's do_work () viene chiamato ANCHE PENSANDO CHE PUNTA A UN OGGETTO INDIANO

Credo che la risposta più votata sia fuorviante - Qualsiasi metodo, virtuale o meno, può avere un'implementazione ignorata nella classe derivata. Con specifico riferimento a C ++ la differenza corretta è l'associazione runtime (quando viene utilizzato il virtuale) e la compilazione (quando non viene utilizzato il virtuale ma un metodo viene ignorato e un puntatore di base è puntato su un oggetto derivato) l'associazione delle funzioni associate.

Sembra che ci sia un altro commento fuorviante che dice:

"Justin, 'puro virtuale' è solo un termine (non una parola chiave, vedi la mia risposta di seguito) usato per indicare" questa funzione non può essere implementata dalla classe base. "

QUESTO È SBAGLIATO! Le funzioni puramente virtuali possono anche avere un corpo E POSSONO ESSERE REALIZZATE! La verità è che la pura funzione virtuale di una classe astratta può essere chiamata staticamente! Due autori molto bravi sono Bjarne Stroustrup e Stan Lippman .... perché hanno scritto la lingua.


2
Sfortunatamente quando una risposta inizia a essere votata, tutti gli altri saranno ignorati. Anche se potrebbero essere migliori.
Ten .orf

3

Una funzione virtuale è una funzione membro dichiarata in una classe base e ridefinita dalla classe derivata. Le funzioni virtuali sono gerarchiche in ordine di eredità. Quando una classe derivata non sostituisce una funzione virtuale, viene utilizzata la funzione definita all'interno della sua classe base.

Una funzione virtuale pura è quella che non contiene alcuna definizione relativa alla classe base. Non ha implementazione nella classe base. Qualsiasi classe derivata deve sovrascrivere questa funzione.


2

Simula, C ++ e C #, che utilizzano per impostazione predefinita l'associazione di metodi statici, il programmatore può specificare che determinati metodi dovrebbero utilizzare l'associazione dinamica etichettandoli come virtuali. Il binding dinamico dei metodi è fondamentale per la programmazione orientata agli oggetti.

La programmazione orientata agli oggetti richiede tre concetti fondamentali: incapsulamento, ereditarietà e associazione dinamica dei metodi.

L'incapsulamento consente di nascondere i dettagli di implementazione di un'astrazione dietro una semplice interfaccia.

L'ereditarietà consente di definire una nuova astrazione come un'estensione o un perfezionamento di alcune astrazioni esistenti, ottenendo automaticamente alcune o tutte le sue caratteristiche.

L'associazione dinamica del metodo consente alla nuova astrazione di mostrare il suo nuovo comportamento anche se utilizzata in un contesto che prevede la vecchia astrazione.


1

I metodi virtuali POSSONO essere sovrascritti dalle classi derivate, ma necessitano un'implementazione nella classe base (quella che verrà sovrascritta)

I metodi virtuali puri non hanno implementato la classe base. Devono essere definiti da classi derivate. (Quindi tecnicamente sovrascritto non è il termine giusto, perché non c'è nulla da ignorare).

Virtual corrisponde al comportamento java predefinito, quando la classe derivata sovrascrive un metodo della classe base.

I metodi virtuali puri corrispondono al comportamento dei metodi astratti all'interno delle classi astratte. E una classe che contiene solo metodi e costanti virtuali puri sarebbe il cpp-pendente di un'interfaccia.


0

Pura funzione virtuale

prova questo codice

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

Nella classe anotherClass rimuovere la funzione sayHellow ed eseguire il codice. si otterrà un errore! Perché quando una classe contiene una funzione virtuale pura, nessun oggetto può essere creato da quella classe ed è ereditato, quindi la sua classe derivata deve implementare quella funzione.

Funzione virtuale

prova un altro codice

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

Qui la funzione sayHellow è contrassegnata come virtuale nella classe base. Dice il compilatore che prova a cercare la funzione nella classe derivata e implementa la funzione. Se non lo trovi, esegui quello di base. Grazie


Ah ah, mi ci sono voluti 30 lunghi secondi per capire cosa c'è che non va qui ... HelloW :)
hans

0

"Una funzione virtuale o un metodo virtuale è una funzione o un metodo il cui comportamento può essere sovrascritto all'interno di una classe ereditaria da una funzione con la stessa firma" - wikipedia

Questa non è una buona spiegazione per le funzioni virtuali. Perché, anche se un membro non è virtuale, ereditare le classi può sovrascriverlo. Puoi provare a vederlo da solo.

La differenza si mostra quando una funzione accetta una classe base come parametro. Quando si assegna una classe ereditaria come input, tale funzione utilizza l'implementazione della classe base della funzione overriden. Tuttavia, se tale funzione è virtuale, utilizza quella implementata nella classe derivante.


0
  • Le funzioni virtuali devono avere una definizione in classe base e anche in classe derivata ma non necessaria, ad esempio la funzione ToString () o toString () è virtuale, quindi è possibile fornire la propria implementazione sovrascrivendola in classi definite dall'utente.

  • Le funzioni virtuali sono dichiarate e definite in classe normale.

  • La funzione virtuale pura deve essere dichiarata che termina con "= 0" e può essere dichiarata solo in classe astratta.

  • Una classe astratta che ha una (e) funzione (i) virtuale (i) pura (e) non può avere una (e) definizione (e) di quelle funzioni virtuali pure, quindi implica che l'implementazione deve essere fornita nelle classi derivate da quella classe astratta.


Stessa nota di @rashedcs: in effetti una pura funzione virtuale può avere la sua definizione ...
Jarek C
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.