2011-02-03 17 views
8

So che le classi derivate possono semplicemente "ridefinire" le funzioni membro di classe di base e che quando la funzione di un oggetto classe derivato è chiamata, la funzione definita in viene utilizzata la classe derivata, ma ... Questo non rende la parola chiave "virtuale" ridondante? Ho letto di alcune differenze ovviamente significative tra questi due casi (es .: se si ha un puntatore di classe base che punta a una classe derivata e si chiama una funzione, se è virtuale la funzione classe derivata sarà chiamata, ma in caso contrario, verrà chiamata la funzione della classe base).Domanda di polimorfismo/ereditarietà C++: ridefinizione delle funzioni di base vs funzioni virtuali

In un altro modo, qual è lo scopo di ridefinire le funzioni membro come funzioni non virtuali ed è una pratica comunemente utilizzata ?

Personalmente, mi sembra che sarebbe solo molto confuso.

Grazie!

+2

Suoni come se fosse necessario [un buon libro C++] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) –

risposta

1

Funzionerà per un'istanza della classe derivata e un puntatore alla classe derivata. Tuttavia, se si passa la classe derivata in una funzione che prende un puntatore su Base, verrà chiamata la versione Base della funzione. Questo probabilmente non è desiderabile. Per esempio, il seguente ritornerà 5

#include "stdafx.h" 
#include <conio.h> 
#include <iostream> 

class Base 
{ 
public: 
    int Foo(){return 5;} 
}; 

class Derived:public Base 
{ 
    int Foo(){return 6;} 
}; 

int Func(Base* base) 
{ 
    return base->Foo(); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Derived asdf; 

    std::cout << Func(&asdf); 
    getch(); 

    return 0; 
} 

Ciò è dovuto il modo in cui lavori virtuali. Quando un oggetto ha una chiamata virtuale, la funzione corretta viene cercata nella tabella v quando si chiama la funzione virtuale. Altrimenti non hai davvero ereditarietà, hai il puntatore Base che agisce come la classe base e non la classe derivata.

4

L'approccio più comune sui linguaggi OOP più comuni (Java, SmallTalk, Python, ecc.) Deve avere, per impostazione predefinita, ogni funzione membro come virtual.

Lo svantaggio è che c'è una piccola penalizzazione delle prestazioni per ogni volta che viene effettuata una chiamata virtuale. Per questo motivo, C++ ti permette di scegliere se vuoi che i tuoi metodi siano definiti come virtuali, o meno.

Tuttavia, c'è una differenza molto importante tra un metodo virtuale e un metodo non virtuale. Ad esempio:

class SomeClass { ... }; 
class SomeSubclassOfSomeClass : public SomeClass { ... }; 
class AnotherSubclassOfSomeClass : public SomeClass { ... }; 

SomeClass* p = ...; 

p->someVirtualMethod(); 

p->someNonVirtualMethod(); 

Il codice effettivo eseguito quando la chiamata someVirtualMethod è fatto dipende dal tipo concreto del puntatore riferimento p, a seconda interamente SomeClass sottoclassi ridefinizione.

Ma il codice eseguito sulla chiamata someNonVirtualMethod è chiaro: sempre quello su SomeClass, poiché il tipo della variabile p è SomeClass.

0

E 'importante fare attenzione quando distruttori sono contrassegnati come non-virtuale e "sovrascritte" nelle classi derivate - è possibile la classe non può ripulire correttamente se il distruttore viene richiamato su un puntatore alla classe base.

0

sembra che non hai scoperto il potere di polimorfismo.Il polimorfismo funziona in questo modo:

Si dispone di una funzione che accetta un puntatore di una classe base, è possibile chiamarlo passando in oggetti di classe derivati, in base alla diversa implementazione della classe derivata, la funzione agirà in modo diverso. È un modo meraviglioso perché la funzione è stabile, anche se espandiamo la gerarchia dell'albero ereditario.

senza questo meccanismo, non è possibile ottenere questo. E questo meccanismo ha bisogno di "virtuale"

1

Indirizzamento: "In altre parole, qual è lo scopo di ridefinire le funzioni membro come funzioni non virtuali ed è una pratica comunemente utilizzata?"

Beh, non è possibile. Se il metodo della classe base è virtuale, lo è anche il metodo della classe derivata corrispondente, se esiste, indipendentemente dal fatto che venga utilizzata o meno la parola chiave "virtuale".

Quindi: "Questo non rende la '' parola chiave ridondante?" Virtuale. Sì, è ridondante nel metodo classe derivata, ma non nella classe base.

Tuttavia, notare che è inusuale (essere educato) per desiderare di avere un metodo non virtuale e quindi ridefinire la la una classe derivata.

2

Sembra che già conosce la differenza tra i metodi virtuali e non virtuali, quindi non andrà in quella come altri hanno. La domanda è: quando un metodo non virtuale sarebbe più utile di uno virtuale?

ci sono casi in cui non si desidera il sovraccarico di avere un puntatore vtable incluso in ogni oggetto, in modo da prendere dolori per assicurarsi che non ci siano non metodi virtuali nella classe. Prendiamo ad esempio una classe che rappresenta un punto e ha due membri, x e - potresti avere una collezione molto grande di questi punti, e un puntatore vtable aumenterebbe le dimensioni dell'oggetto di almeno il 50%.

0

C++ scoping e il nome-di ricerca regole consentono cose molto strane, e metodi non sono qui da solo. Infatti Nascondere (o shadowing) possono verificarsi in molte situazioni diverse:

int i = 3; 
for (int i = 0; i != 5; ++i) { ... } // the `i` in `for` hides the `i` out of it 

struct Base 
{ 
    void foo(); 
    int member; 
}; 

struct Derived: Base 
{ 
    void foo(); // hides Base::foo 
    int member; // hides Base::member 
}; 

Perché allora? Per resilienza.

Quando si modifica una classe Base che conosco non di tutte le sue possibili figli. A causa della regola nascondere (e nonostante la confusione può creare), posso aggiungere un attributo o un metodo e utilizzarlo senza una cura in tutto il mondo:

  • Il mio invito sarà sempre chiamare il metodo Base, se qualche bambino ignorare o no
  • una chiamata sul bambino sarà inalterato, quindi io non improvvisamente in crash il codice di qualche altro programmatore

e 'evidente che, se si guarda il programma come un lavoro finito non rende senso, ma i programmi si stanno evolvendo e la regola nascosta facilita l'evoluzione.

0

Il modo più semplice per spiegarlo è probabilmente questo:

virtuale fa qualche ricerca per voi, con l'aggiunta di una tabella di ricerca virtuale.

In altre parole, se non avete avuto la parola chiave virtuale e overrode un metodo, si sarebbe ancora dovuto chiamare manualmente che il metodo [mi perdoni se la mia memoria per la sintassi C++ è un po 'arrugginito in punti] :

class A { void doSomething() { cout << "1"; } } 
class B: public A { void doSomething() { cout << "2"; } } 
class C: public A { void doSomething() { cout << "3"; } } 

void someOtherFunc(A* thing) { 
    if (typeid(thing) == typeid(B)) { 
     static_cast<B*>(thing)->doSomething(); 
    } else if (typeid(thing) == typeid(C)) { 
     static_cast<C*>(thing)->doSomething(); 
    } else { 
     // not a derived class -- just call A's method 
     thing->doSomething(); 
    } 
} 

Si potrebbe ottimizzare questo un po '(per migliorare la leggibilità e la performance, più probabile), utilizzando una tabella di ricerca:

typedef doSomethingWithAnA(A::*doSomethingPtr)(); 
map<type_info, doSomethingWithAnA> A_doSomethingVTable; 

void someOtherFuncA* thing) { 
    doSomethingWithAnA methodToCall = A_doSomethingVTable[typeid(thing)]; 
    thing->(*methodToCall)(); 
} 

Ora, che è più di un approccio di alto livello. Il compilatore C++ può ovviamente ottimizzarlo un po 'di più, sapendo esattamente che cosa è "type_info" e così via. Quindi probabilmente, invece di quella "mappa" e la ricerca "methodToCall = aDoSomethingVTable [typeid (cosa)]", quindi chiama, ", il compilatore sta inserendo qualcosa di molto più piccolo e veloce, come" doSomethingWithAnA * A_doSomethingVTable; "seguito da" A_doSomethingTablething -> type_number"

Quindi hai ragione che C++ non ha veramente bisogno virtuale, ma lo fa aggiungere un sacco di zucchero sintattico per rendere la vita più facile, e può ottimizzare meglio troppo

che.. Detto questo, penso ancora che il C++ sia un linguaggio orribilmente sorpassato, con molte complicazioni inutili.Per esempio, Virtual potrebbe (e probabilmente dovrebbe) essere assunto per impostazione predefinita e ottimizzato laddove non necessario.Una parola chiave "override" simile a Scala sarebbe molto più utile più che "virtuale".

Problemi correlati