Ignora in modo sicuro le funzioni virtuali C ++


100

Ho una classe base con una funzione virtuale e voglio sovrascrivere quella funzione in una classe derivata. C'è un modo per fare in modo che il compilatore controlli se la funzione che ho dichiarato nella classe derivata sostituisce effettivamente una funzione nella classe base? Vorrei aggiungere qualche macro o qualcosa che assicuri di non dichiarare accidentalmente una nuova funzione, invece di sovrascrivere quella vecchia.

Prendi questo esempio:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Qui parent::handle_event()viene chiamato invece di child::handle_event(), perché il metodo del bambino manca la constdichiarazione e quindi dichiara un nuovo metodo. Potrebbe anche essere un errore di battitura nel nome della funzione o qualche piccola differenza nei tipi di parametri. Può anche accadere facilmente se l'interfaccia della classe base cambia e da qualche parte qualche classe derivata non è stata aggiornata per riflettere la modifica.

C'è un modo per evitare questo problema, posso in qualche modo dire al compilatore oa qualche altro strumento di controllarlo per me? Eventuali flag utili per il compilatore (preferibilmente per g ++)? Come eviti questi problemi?


2
Ottima domanda, ho cercato di capire perché la mia funzione di classe figlio non viene chiamata da un'ora!
Akash Mankar

Risposte:


89

A partire da g ++ 4.7, comprende la nuova overrideparola chiave C ++ 11 :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};

@hirschhornsalz: ho scoperto che quando si implementa la funzione handle_event e si aggiunge l'override alla fine dell'implementazione della funzione, g ++ restituisce un errore; se fornisci un'implementazione della funzione inline nella dichiarazione di classe che segue la parola chiave override, va tutto bene. Perché?
h9uest

3
@ h9uest overridedeve essere utilizzato nella definizione. Un'implementazione in linea è sia definizione che implementazione, quindi va bene.
Gunther Piez

@hirschhornsalz sì, ho ricevuto lo stesso messaggio di errore da g ++. Una nota a margine, però: sia tu che g ++ error msg avete utilizzato il termine "definizione di classe" - non dovremmo usare "dichiarazione" (coppia {dichiarazione, definizione})? Ti sei reso chiaro in questo particolare contesto dicendo "definizione e implementazione", ma mi chiedo solo perché la comunità di c ++ decide improvvisamente di cambiare i termini sulle classi?
h9uest

20

Qualcosa come la overrideparola chiave di C # non fa parte di C ++.

In gcc, -Woverloaded-virtualavverte di non nascondere una funzione virtuale della classe base con una funzione con lo stesso nome ma una firma sufficientemente diversa da non sovrascriverla. Tuttavia, non ti proteggerà dal non riuscire a sovrascrivere una funzione a causa dell'ortografia errata del nome della funzione stessa.


2
È se ti capita di utilizzare Visual C ++
Steve Rowe

4
L'utilizzo di Visual C ++ non crea overrideuna parola chiave in C ++; potrebbe significare che stai usando qualcosa che può compilare un codice sorgente C ++ non valido, però. ;)
CB Bailey

3
Il fatto che l'override non sia valido C ++ significa che lo standard è sbagliato e non Visual C ++
Jon

2
@ Jon: OK, ora capisco a cosa stavi guidando. Personalmente potrei prendere o lasciare overridefunzionalità in stile C # ; Raramente ho avuto problemi con le sostituzioni non riuscite e sono state relativamente facili da diagnosticare e risolvere. Una cosa con cui non sono d'accordo è che gli utenti di VC ++ dovrebbero usarlo. Preferirei che C ++ assomigliasse a C ++ su tutte le piattaforme anche se un particolare progetto non ha bisogno di essere portabile. Vale la pena di notare che C ++ 0x avrà [[base_check]], [[override]]e [[hiding]]attributi in modo da poter scegliere di ignorare il controllo se lo si desidera.
CB Bailey

5
Stranamente, un paio di anni dopo il commento utilizzando VC ++ non fa overrideuna parola chiave sembra che ha fatto . Bene, non una parola chiave corretta ma un identificatore speciale in C ++ 11. Microsoft ha spinto abbastanza per fare un caso speciale di questo e per seguire il formato generale per gli attributi e overridelo ha reso lo standard :)
David Rodríguez - dribeas

18

Per quanto ne so, non puoi semplicemente renderlo astratto?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Pensavo di aver letto su www.parashift.com che puoi effettivamente implementare un metodo astratto. Il che ha senso per me personalmente, l'unica cosa che fa è costringere le sottoclassi a implementarlo, nessuno ha detto nulla sul fatto che non gli sia permesso di avere un'implementazione stessa.


Solo ora mi sono reso conto che funziona su più che semplici distruttori! Ottima scoperta.
strager

1
Ci sono un paio di potenziali svantaggi in questo: 1) l'altra cosa che segnare uno o più metodi come astratti fa è rendere la classe base non istanziabile, il che potrebbe essere un problema se non fa parte dell'uso previsto della classe. 2) la classe base potrebbe non essere tua da modificare in primo luogo.
Michael Burr

3
Sono d'accordo con Michael Burr. Rendere la classe base astratta non fa parte della domanda. È perfettamente ragionevole avere una classe base con funzionalità in un metodo virtuale, che si desidera sostituire con una classe derivata. Ed è altrettanto ragionevole voler proteggere da un altro programmatore che rinomina la funzione nella classe base e che la classe derivata non la sovrascriva più. L'estensione "override" di Microsoft è inestimabile in questo caso. Mi piacerebbe vederlo aggiunto allo standard, perché sfortunatamente non c'è un buon modo per farlo senza di esso.
Brian

Ciò impedisce anche che il metodo di base (diciamo BaseClass::method()) venga chiamato nell'implementazione derivata (diciamo DerivedClass::method()), ad esempio per un valore predefinito.
Narcolessico

11

In MSVC, puoi usare la overrideparola chiave CLR anche se non stai compilando per CLR.

In g ++, non esiste un modo diretto per imporlo in tutti i casi; altre persone hanno dato buone risposte su come rilevare le differenze di firma utilizzando -Woverloaded-virtual. In una versione futura, qualcuno potrebbe aggiungere una sintassi simile __attribute__ ((override))o equivalente utilizzando la sintassi C ++ 0x.


9

In MSVC ++ puoi usare la parola chiaveoverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override funziona sia per codice nativo che CLR in MSVC ++.


5

Rendi astratta la funzione, in modo che le classi derivate non abbiano altra scelta che sovrascriverla.

@Ray Il tuo codice non è valido.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Le funzioni astratte non possono avere corpi definiti in linea. Deve essere modificato per diventare

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }

3

Suggerirei un leggero cambiamento nella tua logica. Potrebbe funzionare o meno, a seconda di ciò che devi realizzare.

handle_event () può ancora fare il "noioso codice predefinito" ma invece di essere virtuale, nel punto in cui vuoi che esegua il "nuovo codice eccitante" fai chiamare la classe base un metodo astratto (cioè deve essere sovrascritto) che verrà fornito dalla tua classe discendente.

EDIT: E se in seguito decidi che alcune delle tue classi discendenti non hanno bisogno di fornire un "nuovo codice entusiasmante", puoi cambiare l'abstract in virtuale e fornire un'implementazione della classe base vuota di quella funzionalità "inserita".


2

Il tuo compilatore potrebbe avere un avviso che può generare se una funzione della classe base viene nascosta. Se lo fa, abilitalo. Ciò catturerà i conflitti di const e le differenze negli elenchi di parametri. Purtroppo questo non rivelerà un errore di ortografia.

Ad esempio, questo è un avviso C4263 in Microsoft Visual C ++.


1

La overrideparola chiave C ++ 11 quando viene utilizzata con la dichiarazione di funzione all'interno della classe derivata, forza il compilatore a verificare che la funzione dichiarata stia effettivamente sovrascrivendo alcune funzioni della classe base. In caso contrario, il compilatore genererà un errore.

Quindi è possibile utilizzare lo overridespecificatore per garantire il polimorfismo dinamico (sostituzione della funzione).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
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.