2014-05-19 8 views
7

This è fondamentalmente una copia dell'esempio fornito in Item 21. Overriding Virtual Functions nel libro di Herb Sutter Exceptional C++.Che cosa ha a che fare con l'overloading delle funzioni?

#include <iostream> 
#include <complex> 
using namespace std; 
class Base 
{ 
public: 
    virtual void f(int); 
    virtual void f(double); 
    virtual ~Base() {}; 
}; 

void Base::f(int) { cout << "Base::f(int)" << endl; } 
void Base::f(double) { cout << "Base::f(double)" << endl; } 

class Derived: public Base { 
public: 
    void f(complex<double>); 
}; 

void Derived::f(complex<double>) { cout << "Derived::f(complex)" << endl; } 

int main() 
{ 
    Base* pb = new Derived; 
    pb->f(1.0); 
    delete pb; 
} 

Le stampe codice Base::f(double) e non ho problemi con quello. Ma non riuscivo a capire la spiegazione data dall'autore sulla parte superiore della pagina 122 (corsivo è mio):

È interessante notare che, anche se la base * pb punta a un oggetto derivato, ciò richiede Base: : f (double), perché la risoluzione di sovraccarico è eseguita sul tipo statico (qui Base), non sul tipo dinamico (qui derivato).

La mia comprensione è che la chiamata è una chiamata pb->f(1.0) virtuale e Base::f(double) è l'overrider finale per f(double) in Derived. Che cosa ha a che fare con l'overloading delle funzioni?

+2

Cambia pb in un oggetto 'Derivato * 'e osserva cosa accadrà. – dlf

+0

@dlf: e il compilatore dà anche l'avviso :-) – Jarod42

+4

Questo titolo non è molto utile. Immagina cosa penseresti se lo vedessi nei risultati di ricerca. –

risposta

12

La parte delicata ecco che metodi virtuali sono un meccanismo per spedizione la chiamata di funzione, mentre sovraccarico è una funzione che influisce sulla risoluzione della chiamata .

Cioè, per qualsiasi chiamata il compilatore ha bisogno di capire quale metodo dovrebbe essere chiamato (risolverlo); successivamente, e in un'operazione logicamente distinta, ha bisogno di generare codice che chiami l'implementazione corretta di quel metodo (lo invii).

Dalle definizioni di Base e Derived proposta di cui sopra può facilmente ovvio che se f(double) è chiamato un Base* allora la chiamata deve essere inviato a qualsiasi sostituzione derivato (se del caso) in preferenza per l'implementazione di base. Ma rispondendo cioè rispondendo a una domanda del tutto diversa da quella

Quando la fonte dice pb->f(1.0), quale dei metodi chiamati f dovrebbero essere utilizzati per risolvere la chiamata di metodo?

Come spiega Sutter, le specifiche dice che quando si risolve la chiamata del compilatore esaminerà i metodi chiamati f dichiarato sul tipo statico puntato da pb; in questo caso, il tipo statico è Base* pertanto i sovraccarichi (non sostituiti!) dichiarati su Derived non verranno considerati affatto. Tuttavia, se il metodo a cui la chiamata si risolve è virtual, l'implementazione possibile fornita su Derived verrà utilizzata come previsto.

+3

E in realtà deve essere così: potresti non avere il codice sorgente per la dichiarazione della funzione virtuale derivata effettiva, e infatti quella funzione di sovrascrittura potrebbe non essere stata ancora scritta, quindi (a) non potresti ottenere il suo di default comunque e (b) anche se fosse possibile, sarebbe probabilmente profondamente sorprendente che il tuo valore predefinito cambi dinamicamente in fase di esecuzione a valori che non potresti mai testare. - Una cosa che vorrei migliorare riguardo al testo che mi hai citato è che è più corretto dire "name lookup" piuttosto che "overload resolution" ma l'analisi è corretta e il risultato è lo stesso. –

3

Il motivo di questo esempio è interessante è perché, se pb erano un Derived* invece di un Base*, o se il compilatore potrebbe usare in qualche modo il tipo dinamico piuttosto che il tipo statico per eseguire la risoluzione di sovraccarico, sarebbe partita la chiamata a pb->f(1.0) a void Derived::f(complex<double>) (complex<double> può essere implicitamente costruito da double). Questo perché la presenza di una funzione denominata f in una classe derivata nasconde in modo efficace qualsiasi sovraccarico della classe base con lo stesso nome, anche se gli elenchi degli argomenti sono diversi.Ma poiché il tipo statico di pb è in realtà Base*, ciò non accade.

0

In questo esempio, nonostante l'occorrenza ripetuta di virtual, non esiste alcun metodo che ignori affatto; il metodo f nella classe derivata ha la precedenza su nessuno di quelli della classe base, poiché i tipi di argomenti non corrispondono. Data questa circostanza, non è possibile che la chiamata di pb->f possa mai richiamare il metodo (univoco) Derived::f. La risoluzione del sovraccarico/ricerca del nome (che considera solo i metodi del tipo statico di pb->f) deve scegliere tra i due metodi dichiarati come Base::f e nell'esempio sceglierà quello con il tipo di argomento double. (In fase di esecuzione questo potrebbe finire per chiamare una sostituzione, se definita in una classe derivata diverso rispetto Derived, e se l'esempio viene modificata in modo che pb potrebbe forse indicare un oggetto di tale un'altra classe derivata.)

Un problema distinto è che i metodi denominati f in Base e Derived non sono considerati contemporaneamente per la risoluzione di sovraccarico se f viene chiamato da un'espressione di tipo Derived, questa volta perché i metodi nella classe di base sono nascosti dal dichiarazione di f in Derived, quindi non sono disponibili per tali chiamate. Penso che questo nascondimento possa essere evitato dichiarando using Base::f;, che "solleva" i metodi Base::f in Derived come se fossero anche stati dichiarati lì (ma ammetto di non conoscere i dettagli di questo meccanismo, suppongo che gli ascensori sarebbero quindi sovrascrittivi del virtuale metodo base con lo stesso tipo di argomento, ma fa poca differenza perché gli ascensori si riferiscono comunque all'implementazione nella classe base.