Qual è il punto di una funzione virtuale pura privata?


139

Mi sono imbattuto nel seguente codice in un file di intestazione:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

Per me, ciò implica che la Engineclasse o una classe derivata da essa deve fornire l'implementazione per quelle funzioni virtuali pure. Ma non pensavo che le classi derivate potessero avere accesso a quelle funzioni private per poterle reimplementare, quindi perché renderle virtuali?

Risposte:


209

La domanda nell'argomento suggerisce una confusione piuttosto comune. La confusione è abbastanza comune, che le FAQ del C ++ hanno difeso a lungo l'uso dei virtual privati, perché la confusione sembrava essere una cosa negativa.

Quindi, per eliminare prima la confusione: Sì, le funzioni virtuali private possono essere ignorate nelle classi derivate. I metodi delle classi derivate non possono chiamare funzioni virtuali dalla classe base, ma possono fornire la propria implementazione per loro. Secondo Herb Sutter, avendo un'interfaccia pubblica non virtuale nella classe base e un'implementazione privata che può essere personalizzata nelle classi derivate, consente una migliore "separazione delle specifiche dell'interfaccia dalle specifiche del comportamento personalizzabile dell'implementazione". Puoi leggere di più a riguardo nel suo articolo "Virtuality" .

C'è comunque un'altra cosa interessante nel codice che hai presentato, che merita più attenzione, secondo me. L'interfaccia pubblica è costituita da un insieme di funzioni non virtuali sovraccaricate e tali funzioni chiamano funzioni virtuali non pubbliche e non sovraccaricate. Come al solito nel mondo C ++ è un linguaggio, ha un nome e ovviamente è utile. Il nome è (sorpresa, sorpresa!)

"Virtual non sovraccarichi pubblici Chiamate virtuali non sovraccarichi protette"

Aiuta a gestire correttamente la regola dei nascondigli . Puoi leggere di più qui , ma cercherò di spiegarlo a breve.

Immagina che le funzioni virtuali della Engineclasse siano anche la sua interfaccia ed è un insieme di funzioni sovraccariche che non è puro virtuale. Se fossero puramente virtuali, si potrebbe ancora riscontrare lo stesso problema, come descritto di seguito, ma inferiore nella gerarchia di classi.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

Ora supponiamo che tu voglia creare una classe derivata e devi fornire una nuova implementazione solo per il metodo, che accetta due ints come argomenti.

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

Se hai dimenticato di inserire la dichiarazione di utilizzo nella classe derivata (o di ridefinire il secondo sovraccarico), potresti avere dei problemi nello scenario seguente.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Se non hai impedito il nascondimento dei Enginemembri, la dichiarazione:

myV8->SetState(5, true);

chiamerebbe void SetState( int var, int val )dalla classe derivata, convertendosi truein int.

Se l'interfaccia non è virtuale e l'implementazione virtuale non è pubblica, come nel tuo esempio, l'autore della classe derivata ha un problema in meno a cui pensare e può semplicemente scrivere

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};

perché la funzione virtuale deve essere privata? Può essere pubblico?
Ricco

Mi chiedo se le linee guida fornite da Herb Sutter nel suo articolo "Virtuality" siano ancora valide oggi?
Nurabha,

@Rich Potresti, ma rendendoli non pubblici, puoi esprimere il loro intento in modo più chiaro. In primo luogo, mostra una separazione di preoccupazioni se si tiene fede nel rendere l'interfaccia pubblica e l'implementazione non pubblica. In secondo luogo, se si desidera che le classi ereditarie siano in grado di chiamare le implementazioni di base, è possibile dichiararle protette; se vuoi solo che forniscano le proprie implementazioni senza chiamare quelle di base, le rendi private.
Dan,

43

La funzione virtuale pura privata è la base del linguaggio dell'interfaccia non virtuale (OK, non è assolutamente sempre pura virtuale, ma comunque virtuale lì). Naturalmente, questo viene utilizzato anche per altre cose, ma lo trovo molto utile (in due parole: in una funzione pubblica, potresti mettere alcune cose comuni (come la registrazione, le statistiche, ecc.) All'inizio e alla fine della funzione e quindi "nel mezzo" per chiamare questa funzione virtuale privata, che sarà diversa per la specifica classe derivata.

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Virtual puro : obbliga solo le classi derivate a implementarlo.

EDIT : Maggiori informazioni su questo: Wikipedia :: NVI-idiom


17

Bene, ciò consentirebbe a una classe derivata di implementare una funzione che la classe base (contenente la dichiarazione di funzione virtuale pura) può chiamare.


5
che solo la classe base può chiamare!
underscore_d

4

EDIT: dichiarazioni chiarite sulla capacità di scavalcare e sulla possibilità di accedere / invocare.

Sarà in grado di sovrascrivere quelle funzioni private. Ad esempio, il seguente esempio forzato funziona ( EDIT: ha reso privato il metodo della classe derivata e elimina l'invocazione del metodo della classe derivata main()per dimostrare meglio l'intento del modello di progettazione in uso. ):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtuali metodi in una classe di base come quelli nel codice vengono generalmente utilizzati per implementare il modello di progettazione Metodo modello . Tale modello di progettazione consente di modificare il comportamento di un algoritmo nella classe base senza modificare il codice nella classe base. Il codice precedente in cui i metodi della classe base vengono richiamati tramite un puntatore della classe base è un semplice esempio del modello Metodo modello.


Capisco, ma se le classi derivate hanno comunque una sorta di accesso, perché preoccuparsi di renderle private?
BeeBand,

@BeeBand: l'utente avrebbe accesso alle sostituzioni del metodo virtuale della classe derivata pubblica, ma non avrebbe accesso a quelle della classe base. L'autore della classe derivata in questo caso potrebbe mantenere privato anche il metodo virtuale. In effetti apporterò la modifica nel codice di esempio sopra per enfatizzarlo. Ad ogni modo, potrebbero sempre ereditare pubblicamente e sovrascrivere i metodi virtuali della classe di base privata, ma avrebbero comunque accesso solo ai propri metodi virtuali di classe derivati. Nota che sto facendo una distinzione tra override e access / invocation.
Vuoto

perché ti sbagli. la visibilità dell'ereditarietà tra le classi Enginee DerivedEnginenon ha nulla a che fare con ciò che DerivedEnginepuò o non può prevalere (o accedere, per quella materia).
Wilhelmtell,

@wilhelmtell: sospiro Naturalmente hai ragione. Aggiornerò la mia risposta di conseguenza.
Vuoto

3

Il metodo virtuale privato viene utilizzato per limitare il numero di classi derivate che possono sostituire la funzione specificata. Le classi derivate che devono sovrascrivere il metodo virtuale privato dovranno essere amiche della classe base.

Una breve spiegazione può essere trovata su DevX.com .


MODIFICA Un metodo virtuale privato viene effettivamente utilizzato nel modello Metodo modello . Le classi derivate possono sovrascrivere il metodo virtuale privato ma le classi derivate non possono chiamarlo metodo virtuale privato di classe base (nel tuo esempio SetStateBoole SetStateInt). Solo la classe base può effettivamente chiamare il suo metodo virtuale privato ( solo se le classi derivate devono invocare l'implementazione di base di una funzione virtuale, rendere la funzione virtuale protetta ).

Un articolo interessante può essere trovato sulla Virtualità .


2
@Gentleman ... hmmm scorri verso il basso fino al commento di Colin D Bennett. Sembra pensare che "Una funzione virtuale privata può essere sovrascritta da classi derivate, ma può essere chiamata solo all'interno della classe base". Anche @Michael Goldshteyn la pensa così.
BeeBand,

Immagino che tu abbia dimenticato il principio che una classe basata privata non può essere vista dalla sua classe derivata. Queste sono le regole OOP e si applicano a tutte le lingue che sono OOP. Affinché una classe derivata possa implementare il suo metodo virtuale privato di classe base, deve friendappartenere a una classe base. Qt ha adottato lo stesso approccio quando ha implementato il proprio modello di documento DOM XML.
Buhake Sindi,

@Gentleman: No, non l'ho dimenticato. Ho fatto un refuso nel mio commento. Invece di "accesso ai metodi della classe base" avrei dovuto scrivere "può sovrascrivere i metodi della classe base". La classe derivata può certamente sovrascrivere il metodo della classe di base virtuale privata anche se non può accedere a quel metodo della classe di base. L'articolo DevX.com che hai indicato non era corretto (eredità pubblica). Prova il codice nella mia risposta. Nonostante il metodo della classe di base virtuale privata, la classe derivata è in grado di sovrascriverla. Non confondiamo la possibilità di sovrascrivere un metodo di classe di base virtuale privata con la possibilità di invocarlo.
Vuoto

@Gentleman: @wilhelmtell ha sottolineato l'errore nella mia risposta / commento. La mia affermazione sull'eredità che influisce sull'accessibilità della classe derivata del metodo della classe base era off. Ho rimosso il commento offensivo alla tua risposta.
Vuoto

@Void, vedo che una classe derivata può sovrascrivere il suo metodo virtuale privato di classe base ma non può usarlo. Quindi, questo è essenzialmente un modello di modello.
Buhake Sindi,

0

TL; risposta DR:

Puoi trattarlo come un altro livello di incapsulamento - a metà tra protetto e privato : non puoi chiamarlo dalla classe figlio, ma puoi sovrascriverlo.

È utile quando si implementa il modello di progettazione Metodo modello. È possibile utilizzare protetto , ma privato insieme a virtuale può essere considerato una scelta migliore, a causa del migliore incapsulamento.

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.