Metodo virtuale privato in C ++


125

Qual è il vantaggio di rendere virtuale un metodo privato in C ++?

L'ho notato in un progetto C ++ open source:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};

9
Penso che la domanda sia al contrario. Il motivo per creare qualcosa di virtuale è sempre lo stesso: consentire alle classi derivate di sovrascriverlo. Quindi la domanda dovrebbe essere: qual è il vantaggio di rendere privato un metodo virtuale? A cui la risposta è: rendi tutto privato per impostazione predefinita. :-)
ShreevatsaR

1
@ShreevatsaR Ma non hai nemmeno risposto alla tua stessa domanda ...
Spencer

@ShreevatsaR Pensavo intendessi all'indietro in un modo diverso: qual è il vantaggio di rendere un metodo virtuale non privato?
Peter - Ripristina Monica il

Risposte:


116

Herb Sutter lo ha spiegato molto bene qui .

Linea guida n. 2: preferisci rendere private le funzioni virtuali.

Ciò consente alle classi derivate di sovrascrivere la funzione per personalizzare il comportamento secondo necessità, senza esporre ulteriormente le funzioni virtuali direttamente rendendole richiamabili dalle classi derivate (come sarebbe possibile se le funzioni fossero solo protette). Il punto è che esistono funzioni virtuali per consentire la personalizzazione; a meno che non debbano essere invocati direttamente dal codice delle classi derivate, non è necessario renderli nient'altro che privati


Come puoi intuire dalla mia risposta, penso che la linea guida n. 3 di Sutter spinga piuttosto la linea guida n. 2 fuori dalla finestra.
Spencer

66

Se il metodo è virtuale, può essere sovrascritto dalle classi derivate, anche se private. Quando viene chiamato il metodo virtuale, verrà richiamata la versione sovrascritta.

(Contrariamente a Herb Sutter citato da Prasoon Saurav nella sua risposta, la C ++ FAQ Lite raccomanda contro i virtuali privati , soprattutto perché spesso confonde le persone.)


41
Sembra che la C ++ FAQ Lite abbia da allora cambiato la sua raccomandazione: " la C ++ FAQ in precedenza raccomandava di utilizzare virtual protette piuttosto che private virtuals. Tuttavia l'approccio privato virtuale è ora abbastanza comune che la confusione dei novizi è meno preoccupante. "
Zack The Umano

19
La confusione degli esperti, tuttavia, rimane una preoccupazione. Nessuno dei quattro professionisti del C ++ seduti accanto a me era a conoscenza di virtual private.
Newtonx

12

Nonostante tutte le chiamate a dichiarare privato un membro virtuale, l'argomento semplicemente non regge. Spesso, l'override di una classe derivata di una funzione virtuale dovrà chiamare la versione della classe base. Non può se è dichiarato private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

È necessario dichiarare il metodo della classe base protected.

Quindi, devi prendere il brutto espediente di indicare tramite un commento che il metodo dovrebbe essere sovrascritto ma non chiamato.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Così la linea guida n. 3 di Herb Sutter ... Ma il cavallo è comunque fuori dalla stalla.

Quando dichiari qualcosa, protectedti fidi implicitamente dello scrittore di qualsiasi classe derivata per comprendere e utilizzare correttamente le parti interne protette, proprio nel modo in cui una frienddichiarazione implica una fiducia più profonda per i privatemembri.

Gli utenti che si comportano male violando quella fiducia (ad esempio etichettati come "incapaci" perché non si preoccupano di leggere la tua documentazione) hanno solo se stessi da incolpare.

Aggiornare : ho ricevuto un feedback che afferma che è possibile "concatenare" le implementazioni di funzioni virtuali in questo modo utilizzando funzioni virtuali private. Se è così, mi piacerebbe sicuramente vederlo.

I compilatori C ++ che uso sicuramente non permetteranno a un'implementazione di una classe derivata di chiamare un'implementazione di una classe base privata.

Se il comitato C ++ rilassasse il "privato" per consentire questo accesso specifico, sarei tutto per funzioni virtuali private. Allo stato attuale, ci viene ancora consigliato di chiudere a chiave la porta della stalla dopo che il cavallo è stato rubato.


3
Trovo il tuo argomento non valido. Come sviluppatore di un'API, dovresti cercare un'interfaccia difficile da usare in modo errato e non impostare un altro sviluppatore per i tuoi errori nel farlo. Quello che vuoi fare nel tuo esempio potrebbe essere implementato solo con metodi virtuali privati.
sigy

1
Non è quello che stavo dicendo. Ma puoi ristrutturare il tuo codice per ottenere lo stesso effetto senza la necessità di chiamare una funzione di classe base privata
sigy

3
Nel tuo esempio vuoi estendere il comportamento di set_data. Istruzioni m_data = ndata;e cleanup();potrebbe quindi essere considerato un invariante che deve valere per tutte le implementazioni. Quindi rendi cleanup()non virtuale e privato. Aggiungi una chiamata a un altro metodo privato virtuale e punto di estensione della tua classe. Ora non è più necessario che le classi derivate chiamino quelle di base cleanup(), il codice rimane pulito e l'interfaccia è difficile da usare in modo errato.
sigy

2
@sigy Questo sposta solo i pali. Devi guardare oltre l'esempio miminale. Quando ci sono ulteriori discendenti che devono chiamare tutte le cleanup()s nella catena, l'argomento cade a pezzi. O stai consigliando una funzione virtuale extra per ogni discendente della catena? Ick. Anche Herb Sutter consentiva funzioni virtuali protette come scappatoia nella sua linea guida n. 3. Comunque, senza un codice effettivo non mi convincerai mai.
Spencer

2
Allora
accettiamo

9

Mi sono imbattuto per la prima volta in questo concetto leggendo "Effective C ++" di Scott Meyers, Item 35: Considera alternative alle funzioni virtuali. Volevo fare riferimento a Scott Mayers per altri che potrebbero essere interessati.

Fa parte del modello del metodo modello tramite l'idioma dell'interfaccia non virtuale : i metodi rivolti al pubblico non sono virtuali; piuttosto, racchiudono le chiamate al metodo virtuale che sono private. La classe base può quindi eseguire la logica prima e dopo la chiamata della funzione virtuale privata:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Penso che questo sia un modello di progettazione molto interessante e sono sicuro che puoi vedere quanto sia utile il controllo aggiunto.

  • Perché creare la funzione virtuale private? Il motivo migliore è che abbiamo già fornito un publicmetodo di rivestimento.
  • Perché non farlo semplicemente in protectedmodo che io possa usare il metodo per altre cose interessanti? Suppongo che dipenderà sempre dal tuo progetto e da come ritieni che la classe base si adatti. Direi che il creatore della classe derivata dovrebbe concentrarsi sull'implementazione della logica richiesta; tutto il resto è già curato. Inoltre, c'è la questione dell'incapsulamento.

Da una prospettiva C ++, è completamente legittimo sovrascrivere un metodo virtuale privato anche se non sarai in grado di chiamarlo dalla tua classe. Questo supporta il design descritto sopra.


3

Li uso per consentire alle classi derivate di "riempire gli spazi vuoti" per una classe base senza esporre tale buco agli utenti finali. Ad esempio, ho oggetti altamente stateful derivanti da una base comune, che può implementare solo 2/3 della macchina a stati complessiva (le classi derivate forniscono il restante 1/3 a seconda di un argomento del modello, e la base non può essere un modello per altri motivi).

HO BISOGNO di avere la classe base comune per far funzionare correttamente molte delle API pubbliche (sto usando modelli variadici), ma non posso lasciare che l'oggetto esca in libertà. Peggio ancora, se lascio i crateri nella macchina a stati - sotto forma di funzioni virtuali pure - ovunque tranne che in "Privato", consento a un utente intelligente o incapace derivante da una delle sue classi figlie di sovrascrivere metodi che gli utenti non dovrebbero mai toccare. Quindi, ho messo il "cervello" della macchina a stati nelle funzioni virtuali PRIVATE. Quindi i figli immediati della classe base riempiono gli spazi vuoti sui loro override NON virtuali e gli utenti possono utilizzare in sicurezza gli oggetti risultanti o creare le proprie classi derivate ulteriori senza preoccuparsi di rovinare la macchina a stati.

Per quanto riguarda l'argomento secondo cui non dovresti AVERE metodi virtuali pubblici, dico BS. Gli utenti possono sovrascrivere in modo improprio i virtuali privati ​​con la stessa facilità di quelli pubblici: dopotutto stanno definendo nuove classi. Se il pubblico non deve modificare una determinata API, non renderla affatto virtuale in oggetti accessibili pubblicamente.

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.