C ++ Virtual / Pure Virtual Explained

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

Dalla funzione virtuale di Wikipedia …

Una funzione virtuale o un metodo virtuale è una funzione o un metodo il cui comportamento può essere sovrascritto in una class ereditaria da una funzione con la stessa firma

mentre..

Una pura funzione virtuale o puro metodo virtuale è una funzione virtuale che è richiesta per essere implementata da una class derivata che non è astratta “- Wikipedia

Quindi, la funzione virtuale può essere sostituita e il puro virtuale deve essere implementato.

Mi piacerebbe commentare la definizione di Wikipedia di virtuale, come ripetuto da diversi qui. [Nel momento in cui questa risposta è stata scritta,] Wikipedia ha definito un metodo virtuale come uno che può essere sovrascritto in sottoclassi. [Fortunatamente, Wikipedia è stato modificato da allora, e ora lo spiega correttamente.] Non è corretto: qualsiasi metodo, non solo quelli virtuali, può essere sovrascritto in sottoclassi. Ciò che è virtuale è darti il ​​polimorfismo, cioè la capacità di selezionare in fase di esecuzione l’override più derivato di un metodo .

Considera il seguente codice:

 #include  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. 

Derivato sovrascrive ogni metodo di Base: non solo quello virtuale, ma anche quello non virtuale.

Vediamo che quando si ha un puntatore-base-derivato (bDerived), si chiamano le chiamate non virtuali l’implementazione della class base. Questo è risolto in fase di compilazione: il compilatore vede che bDerived è una Base *, che NonVirtual non è virtuale, quindi fa la risoluzione sulla class Base.

Tuttavia, chiamare Virtual chiama l’implementazione della class derivata. A causa della parola chiave virtuale, la selezione del metodo avviene in fase di esecuzione , non in fase di compilazione. Quello che succede qui in fase di compilazione è che il compilatore vede che questa è una Base *, e che sta chiamando un metodo virtuale, quindi inserisce una chiamata al vtable invece della class Base. Questo vtable è istanziato in fase di esecuzione, quindi la risoluzione del tempo di esecuzione per l’override più derivato.

Spero che questo non sia stato troppo confuso. In breve, qualsiasi metodo può essere sovrascritto, ma solo i metodi virtuali forniscono il polimorfismo, ovvero la selezione del tempo di esecuzione della sovrascrittura più derivata. In pratica, tuttavia, ignorare un metodo non virtuale è considerato una ctriggers pratica e raramente utilizzato, quindi molte persone (incluso chiunque abbia scritto quell’articolo di Wikipedia) pensano che solo i metodi virtuali possano essere ignorati.

La parola chiave virtuale dà a C ++ la sua capacità di supportare il polimorfismo. Quando hai un puntatore a un object di una class 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 (stupido), la funzione GetNumberOfLegs () restituisce il numero appropriato in base alla class dell’object per cui è stato chiamato.

Ora, considera la funzione ‘SomeFunction’. Non gli importa di quale tipo di object animale gli viene passato, purché sia ​​derivato da Animal. Il compilatore trasmetterà automaticamente qualsiasi class derivata da Animal ad un animale in quanto è una class 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 Pure Virtual Functions sono principalmente utilizzate per definire:

a) classi astratte

Queste sono classi base in cui devi derivare da esse e quindi implementare le pure funzioni virtuali.

b) interfacce

Queste sono classi ‘vuote’ in cui tutte le funzioni sono pure virtuali e quindi è necessario derivare e quindi implementare tutte le funzioni.

In una class C ++, virtuale è la parola chiave che lo designa, un metodo può essere sovrascritto (cioè implementato da) una sottoclass. 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 sottoclass può sovrascrivere la funzione initShape per eseguire alcuni lavori specializzati:

 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 sottoclass e non sono state implementate dalla class base. Tu definisci un metodo come puro virtuale usando la parola chiave virtuale e aggiungendo a = 0 alla fine della dichiarazione del metodo.

Quindi, se volessi rendere Shape :: initShape pure virtual, dovresti fare quanto segue:

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

Aggiungendo un metodo virtuale puro alla class, la class diventa una class base astratta che è molto utile per separare le interfacce dall’implementazione.

“Virtuale” significa che il metodo può essere sovrascritto in sottoclassi, ma ha un’implementazione richiamabile direttamente nella class base. “Puro virtuale” significa che si tratta di un metodo virtuale senza implementazione direttamente richiamabile. Tale metodo deve essere sovrascritto almeno una volta nella gerarchia di ereditarietà: se una class ha metodi virtuali non implementati, gli oggetti di quella class non possono essere costruiti e la compilazione fallirà.

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

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

In base ai commenti, se la compilazione fallirà è 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() 

Come funziona la parola chiave virtuale?

Supponiamo che l’uomo sia una class 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 virtual non viene utilizzato, lo stesso è determinato staticamente o staticamente dal compilatore, a seconda di quale object sta chiamando. Quindi se un object di Man chiama do_work (), Man’s do_work () viene chiamato ANCHE SE PUNTA A UN OGGETTO INDIANO

Credo che la risposta più votata sia fuorviante – Qualsiasi metodo indipendentemente dal fatto che virtual possa avere un’implementazione sovrascritta nella class derivata. Con specifico riferimento a C ++, la differenza corretta è il tempo di esecuzione (quando virtuale è usato) di associazione e tempo di compilazione (quando il virtuale non viene usato, ma un metodo è sovrascritto e un puntatore di base è puntato su un object derivato) delle funzioni associate.

Sembra esserci un altro commento fuorviante che dice,

“Justin, ‘puro virtuale’ è solo un termine (non una parola chiave, vedi la mia risposta sotto) usato per significare” questa funzione non può essere implementata dalla class base “.

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

Simula, C ++ e C #, che utilizzano il bind del metodo statico per impostazione predefinita, il programmatore può specificare che determinati metodi devono utilizzare l’associazione dynamic etichettandoli come virtuali. L’associazione dynamic dei metodi è fondamentale per la programmazione orientata agli oggetti.

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

L’incapsulamento consente di hide i dettagli di implementazione di un’astrazione dietro una semplice interfaccia.

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

L’associazione dynamic dei metodi consente alla nuova astrazione di mostrare il suo nuovo comportamento anche quando viene utilizzata in un contesto che si aspetta la vecchia astrazione.

I metodi virtuali possono essere sovrascritti derivando le classi, ma hanno bisogno di un’implementazione nella class base (quella che verrà sovrascritta)

I metodi virtuali puri non hanno l’implementazione della class base. Devono essere definiti da classi derivate. (Quindi tecnicamente ignorato non è il termine giusto, perché non c’è nulla da escludere).

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

I metodi Pure Virtual corrispondono al comportamento dei metodi astratti all’interno delle classi astratte. E una class che contiene solo metodi e costanti virtuali puri sarebbe il cpp-pendant di un’interfaccia.

Funzione virtuale pura

prova questo codice

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

In class anotherClass rimuovi la funzione sayHellow ed esegui il codice. si otterrà un errore! Perché quando una class contiene una pura funzione virtuale, nessun object può essere creato da quella class e viene ereditato, quindi la sua class derivata deve implementare quella funzione.

Funzione virtuale

prova un altro codice

 #include  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 class base. Si dice che il compilatore prova a cercare la funzione nella class derivata e implementa la funzione. Se non viene trovata, esegue la base. Grazie

“Una funzione virtuale o un metodo virtuale è una funzione o un metodo il cui comportamento può essere sovrascritto in una class 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, l’ereditare le classi può sovrascriverlo. Puoi provare a vederlo da solo.

La differenza si mostra quando una funzione prende una class base come parametro. Quando si assegna una class ereditante come input, quella funzione utilizza l’implementazione della class base della funzione sovrascritta. Tuttavia, se quella funzione è virtuale, utilizza quella implementata nella class derivante.

  • Le funzioni virtuali devono avere una definizione nella class base e anche nella class derivata ma non necessaria, ad esempio la funzione ToString () o toString () è una Virtual in modo da poter fornire la propria implementazione sovrascrivendola nelle classi definite dall’utente.

  • Le funzioni virtuali sono dichiarate e definite nella class normale.

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

  • Una class astratta che ha una / e funzione / i virtuale / i pura / e non può avere una / e definizione / e di quelle pure funzioni virtuali, quindi implica che l’implementazione debba essere fornita nelle classi che derivano da quella class astratta.

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

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