Pura funzione virtuale con implementazione


176

La mia comprensione di base è che non esiste un'implementazione per una pura funzione virtuale, tuttavia, mi è stato detto che potrebbe esserci un'implementazione per pura funzione virtuale.

class A {
public:
    virtual void f() = 0;
};

void A::f() {
    cout<<"Test"<<endl;
}

Il codice sopra è OK?

Qual è lo scopo di renderlo una pura funzione virtuale con un'implementazione?

Risposte:


215

Una virtualfunzione pura deve essere implementata in un tipo derivato che verrà istanziato direttamente, tuttavia il tipo base può ancora definire un'implementazione. Una classe derivata può chiamare esplicitamente l'implementazione della classe base (se le autorizzazioni di accesso lo consentono) utilizzando un nome con ambito completo (chiamando A::f()nel tuo esempio - if A::f()were publicor protected). Qualcosa di simile a:

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

Il caso d'uso che mi viene in mente è quando c'è un comportamento di default più o meno ragionevole, ma il progettista di classe vuole che quel tipo di comportamento di default venga invocato solo esplicitamente. Può anche essere il caso in cui si desidera che le classi derivate eseguano sempre il proprio lavoro ma che siano anche in grado di chiamare un insieme comune di funzionalità.

Si noti che anche se è consentito dal linguaggio, non è qualcosa che vedo comunemente usato (e il fatto che possa essere fatto sembra sorprendere la maggior parte dei programmatori C ++, anche quelli con esperienza).


1
Hai dimenticato di aggiungere perché sorprende i programmatori: è perché la definizione in linea è vietata dallo standard. Devono essere pure le definizioni dei metodi virtuali deported. (in .inl o .cpp per fare riferimento alle pratiche comuni di denominazione dei file).
v.oddou,

quindi questo metodo di chiamata è uguale al membro del metodo statico che chiama. Una sorta di metodo di classe in Java.
Sany Liew,

2
"non comunemente usato" == cattiva pratica? Stavo cercando lo stesso identico comportamento, cercando di implementare NVI. E NVI mi sembra una buona pratica.
Saskia,

5
Vale la pena sottolineare che rendere A :: f () puro significa che B deve implementare f () (altrimenti B sarebbe astratto e non affidabile). E come sottolinea @MichaelBurr, fornire un'implementazione per A :: f () significa che B può usarlo per definire f ().
fearless_fool

2
IIRC, Scot Meyer ha pubblicato un eccellente articolo sul caso d'uso di questa domanda in uno dei suoi libri classici "Più efficace C ++"
irsis,

75

Per essere chiari, stai fraintendendo cosa = 0; dopo una funzione virtuale significa.

= 0 indica che le classi derivate devono fornire un'implementazione, non che la classe base non possa fornire un'implementazione.

In pratica, quando si contrassegna una funzione virtuale come pura (= 0), è molto scarso fornire una definizione, perché non verrà mai chiamata a meno che qualcuno non lo faccia esplicitamente tramite Base :: Function (...) o se il Il costruttore della classe base chiama la funzione virtuale in questione.


9
Questo non è corretto Se invochi quella pura funzione virtuale nel costruttore della tua pura classe virtuale, verrebbe effettuata una pura chiamata virtuale. Nel qual caso è meglio avere un'implementazione.
rmn

@rmn, Sì, hai ragione sulle chiamate virtuali nei costruttori. Ho aggiornato la risposta. Spero che tutti sappiano non farlo, però. :)
Terry Mahaffey il

3
In effetti, effettuare una chiamata pura di base da un costruttore comporta un comportamento definito dall'implementazione. In VC ++, ciò equivale a un crash _purecall.
Ofek Shilon,

@OfekShilon è corretto - Sarei tentato di chiamarlo anche comportamento indefinito e candidato a cattive pratiche / refactoring del codice (cioè chiamando metodi virtuali all'interno del costruttore). Immagino che abbia a che fare con la coerenza della tabella virtuale, che potrebbe non essere pronta per essere indirizzata al corpo dell'implementazione corretta.
teodron,

1
Nei costruttori e nei distruttori, le funzioni virtuali non sono virtuali.
Jesper Juhl,

20

Il vantaggio è che impone ai tipi derivati ​​di sostituire ancora il metodo ma fornisce anche un'implementazione predefinita o aggiuntiva.


1
Perché dovrei forzare se esiste un'implementazione predefinita? Sembra normale funzione virtuale. Se fosse solo una normale funzione virtuale, posso eseguire l'override e, in caso contrario, verrà fornita un'implementazione predefinita (implementazione di base).
StackExchange123

19

Se si dispone di codice che dovrebbe essere eseguito dalla classe derivante, ma non si desidera che venga eseguito direttamente - e si desidera forzarlo per essere sostituito.

Il tuo codice è corretto, anche se tutto sommato questa non è una funzionalità spesso utilizzata e di solito si vede solo quando si tenta di definire un distruttore virtuale puro, in tal caso è necessario fornire un'implementazione. La cosa divertente è che una volta derivato da quella classe non è necessario scavalcare il distruttore.

Quindi l'unico uso sensato di pure funzioni virtuali è specificare un puro distruttore virtuale come parola chiave "non definitiva".

Il seguente codice è sorprendentemente corretto:

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}

1
I distruttori della classe base sono sempre chiamati comunque, virtuali o no e puri o no; con altre funzioni non è possibile garantire che una funzione virtuale prioritaria chiamerà l'implementazione della classe base indipendentemente dal fatto che la versione della classe base sia pura.
CB Bailey,

1
Quel codice è sbagliato. È necessario definire il dtor al di fuori della definizione della classe a causa di una stranezza della sintassi della lingua.

@Roger: grazie, mi ha aiutato davvero - questo è il codice che ho usato, si compila bene sotto MSVC, ma suppongo che non sarebbe portatile.
Kornel Kisielewicz,


4

Sì questo è corretto. Nel tuo esempio, le classi che derivano da A ereditano sia l'interfaccia f () che un'implementazione predefinita. Ma imponi alle classi derivate di implementare il metodo f () (anche se è solo per chiamare l'implementazione predefinita fornita da A).

Scott Meyers ne discute in Effective C ++ (2nd Edition) Item # 36 Distingue tra eredità dell'interfaccia ed eredità dell'implementazione. Il numero dell'articolo potrebbe essere cambiato nell'ultima edizione.


4

Le funzioni virtuali pure con o senza un corpo significano semplicemente che i tipi derivati ​​devono fornire la propria implementazione.

I corpi delle funzioni virtuali pure nella classe base sono utili se le classi derivate vogliono chiamare l'implementazione della classe base.


2

Il 'virtual void foo () = 0;' la sintassi non significa che non puoi implementare foo () nella classe corrente, puoi. Inoltre non significa che è necessario implementarlo in classi derivate . Prima di schiaffeggiarmi, osserviamo il problema del diamante: (codice implicito, intendiamoci).

class A
{
public: 
    virtual void foo()=0;
    virtual void bar();
}

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

Ora, l'invocazione obj-> foo () si tradurrà in B :: foo () e poi in C :: bar ().

Vedi ... i metodi virtuali puri non devono essere implementati nelle classi derivate (foo () non ha implementazione nella classe C - il compilatore verrà compilato) In C ++ ci sono molte scappatoie.

Spero di poterti aiutare :-)


5
Non deve essere implementato in TUTTE le classi derivate, ma DEVE avere un'implementazione in tutte le classi derivate che si intende istanziare. Non puoi creare un'istanza di un oggetto di tipo Cnel tuo esempio. È possibile creare un'istanza di un oggetto di tipo Dperché ne ottiene l'implementazione fooda B.
Young John,

0

Un caso d'uso importante di avere un metodo virtuale puro con un corpo di implementazione è quando si desidera avere una classe astratta, ma non si dispone di metodi adeguati nella classe per renderlo puro virtuale. In questo caso, puoi rendere virtuale il distruttore della classe e mettere la tua implementazione desiderata (anche un corpo vuoto) per quello. Come esempio:

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

Questa tecnica rende la Fooclasse astratta e, di conseguenza, impossibile creare un'istanza diretta della classe. Allo stesso tempo non è stato aggiunto un metodo virtuale puro aggiuntivo per rendere Fooastratta la classe.

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.