2010-06-03 12 views
13

Quando un metodo è dichiarato come virtual in una classe, i suoi override nelle classi derivate sono considerati automaticamente virtual pure, e il linguaggio C++ rende questa parola chiave virtual opzionale in questo caso:Perché l'opzione 'virtuale' è facoltativa per i metodi sottoposti a override nelle classi derivate?

class Base { 
    virtual void f(); 
}; 
class Derived : public Base { 
    void f(); // 'virtual' is optional but implied. 
}; 

La mia domanda è: Che cosa è la logica per rendere virtual facoltativo?

So che non è assolutamente necessario che il compilatore venga informato, ma penserei che gli sviluppatori potrebbero trarre vantaggio dal fatto che il compilatore abbia applicato tale vincolo.

esempio a volte quando ho letto il codice altrui mi chiedo se un metodo è virtuale e devo rintracciare sue superclassi di determinare che. E alcuni standard di codifica (Google) lo rendono un 'must' per mettere la parola chiave virtual in tutte le sottoclassi.

risposta

10

Sì, sarebbe davvero meglio far sì che il compilatore applicasse il virtuale in questo caso e sono d'accordo che si tratta di un errore di progettazione che viene mantenuto per compatibilità con le versioni precedenti.

Tuttavia c'è un trucco che sarebbe impossibile senza di essa:

class NonVirtualBase { 
    void func() {}; 
}; 

class VirtualBase { 
    virtual void func() = 0; 
}; 

template<typename VirtualChoice> 
class CompileTimeVirtualityChoice : public VirtualChoice { 
    void func() {} 
}; 

Con quanto sopra abbiamo compilazione scelta volta che scendessimo vogliamo virtualità func o no:

CompileTimeVirtualityChoice<VirtualBase> -- func is virtual 
CompileTimeVirtualityChoice<NonVirtualBase> -- func is not virtual 

... ma d'accordo, è un piccolo vantaggio per il costo della ricerca della virtualità di una funzione, e io stesso, cerco sempre di digitare virtuale ovunque, dove applicabile.

+0

Pensa che vorresti rimuovere '= 0' da' NonVirtualBase' :) –

+0

@Matthieu - buona cattura! Oggi sono piuttosto assonnato xD –

2

Dal momento che il linguaggio non può imporre lo stile "buona", C++ in genere non provo nemmeno. Almeno IMO, è una questione aperta se l'inserimento di identificatori ridondanti come questo è buon stile in ogni caso (personalmente, io odio quando sono lì).

(Almeno parti di) standard di codifica di Google possono in alcune circostanze, ma per quanto riguarda il C++ in generale, sono generalmente considerati mediocri consigli al meglio. In una certa misura, ammettono anche che - alcuni di loro dichiarano apertamente che sono davvero lì solo per adattarsi al loro vecchio codice. Altre parti non lo ammettono in modo così diretto, e (per essere del tutto onesti) che l'argomento non avrebbe sostenuto alcuni dei loro standard in ogni caso (vale a dire, alcuni dei quali sembra mancare reale giustificazione).

+0

Non è che il linguaggio "non possa", ma piuttosto che sia contro la filosofia C++. Un compilatore può rilevare una grande varietà di violazioni di stile, incluso se "virtuale" è stato riutilizzato nella classe derivata. –

+0

@ Michael: Forse l'ho detto male, ma il mio intento era di dire che non è in grado di imporre il buon stile in generale, quindi spesso non ci prova, anche nei casi in cui chiaramente potrebbe (far rispettare qualcosa che alcune persone considerano buono stile). –

3

punto debole nel design, sono d'accordo. Penso anche che sarebbe davvero bello se ci fosse una sintassi diversa per due cose diverse:

  1. Dichiarare una funzione virtuale. Cioè la funzione che può essere sovrascritta nella classe derivata. (Questa cosa aggiunge effettivamente una nuova voce di funzione nel vtable.)
  2. Sovrascrittura di una funzione virtuale nella classe derivata.

Con le regole C++ correnti quando si esegue l'override di una funzione, è facile rovinare le cose. Se si configura il nome della funzione (o si commette un errore nel suo elenco di parametri), allora si esegue effettivamente (1) anziché (2).

E non ci sono errori/avvertenze. Basta avere la sorpresa in fase di esecuzione.

+1

Una delle cose che Object Pascal ha avuto ragione ... –

+1

D'accordo, penso che derivi da Bjarne Stroustrup e dal desiderio del comitato di avere il più piccolo possibile un insieme di parole chiave ... riutilizzando così lo stesso per cose diverse. Allo stesso modo, non apprezzo molto il fatto di non avere un avviso ogni volta che si verifica uno shadowing: / –

0

Questa è una buona domanda, e sono certamente d'accordo sul fatto che sia buono redeclare un metodo virtuale nella classe derivata se è stato dichiarato virtuale nella classe base. Mentre ci sono alcune lingue che costruiscono lo stile nella lingua (ad esempio Google Go e, in qualche misura, Python), il C++ non è una di quelle lingue. Mentre è certamente possibile per un compilatore rilevare che una classe derivata non riutilizza la parola chiave "virtuale" per qualcosa dichiarato "virtuale" in una classe base (o, più importante, che la classe derivata dichiara una funzione con lo stesso nome di la classe base e non è stata dichiarata virtuale nella classe base), ci sono, infatti, impostazioni su molti compilatori per emettere avvisi (e anche messaggi di errore) nel caso in cui ciò avvenga. In questa fase, tuttavia, non sarebbe pratico istituire un tale requisito nella lingua in quanto esiste troppo codice che non è così rigoroso. Inoltre, gli sviluppatori possono sempre scegliere di essere più rigorosi della lingua e possono alzare i livelli di avviso del compilatore.

6

Come nota correlata, in C++ 0x si ha l'opzione di imporre l'esplicitazione con le sostituzioni tramite la nuova sintassi dell'attributo.

struct Base { 
    virtual void Virtual(); 
    void NonVirtual(); 
}; 

struct Derived [[base_check]] : Base { 
    //void Virtual(); //Error; didn't specify that you were overriding 
    void Virtual [[override]](); //Not an error 
    //void NonVirtual [[override]](); //Error; not virtual in Base 
    //virtual void SomeRandomFunction [[override]](); //Error, doesn't exist in Base 
}; 

È inoltre possibile specificare quando si intende nascondere un membro tramite l'attributo [[hiding]]. Rende il tuo codice un po 'più prolisso, ma può catturare un sacco di fastidiosi bug in fase di compilazione, ad esempio se hai fatto void Vritual() invece di void Virtual() e hai finito per introdurre una funzione completamente nuova quando intendevi sovrascrivere uno esistente.

Problemi correlati