Reine virtuelle Funktion mit Implementierung

175

Mein grundlegendes Verständnis ist, dass es keine Implementierung für eine reine virtuelle Funktion gibt. Mir wurde jedoch gesagt, dass es möglicherweise eine Implementierung für eine reine virtuelle Funktion gibt.

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

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

Ist der obige Code in Ordnung?

Was ist der Zweck, um es zu einer rein virtuellen Funktion mit einer Implementierung zu machen?

skydoor
quelle

Antworten:

215

Eine reine virtualFunktion muss in einem abgeleiteten Typ implementiert werden, der direkt instanziiert wird. Der Basistyp kann jedoch weiterhin eine Implementierung definieren. Eine abgeleitete Klasse kann die Basisklassenimplementierung explizit aufrufen (wenn Zugriffsrechte es erlauben) durch eine voll scoped Namen (durch den Aufruf A::f()in Ihrem Beispiel - wenn A::f()waren publicoder protected). Etwas wie:

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();
    }

};

Der Anwendungsfall, den ich mir auf Anhieb vorstellen kann, ist, wenn es ein mehr oder weniger vernünftiges Standardverhalten gibt, der Klassendesigner jedoch möchte, dass das Standardverhalten nur explizit aufgerufen wird. Es kann auch der Fall sein, dass abgeleitete Klassen immer ihre eigene Arbeit ausführen sollen, aber auch einen gemeinsamen Satz von Funktionen aufrufen können.

Beachten Sie, dass es, obwohl es von der Sprache erlaubt ist, nicht häufig verwendet wird (und die Tatsache, dass dies möglich ist, scheint die meisten C ++ - Programmierer, selbst erfahrene, zu überraschen).

Michael Burr
quelle
1
Sie haben vergessen hinzuzufügen, warum es Programmierer überrascht: Es liegt daran, dass die Inline-Definition standardmäßig verboten ist. Reine virtuelle Methodendefinitionen müssen sein deported. (entweder in .inl oder .cpp, um auf gängige Methoden zur Benennung von Dateien zu verweisen).
v.oddou
Diese aufrufende Methode entspricht also dem Aufruf des statischen Methodenmitglieds. Eine Art Klassenmethode in Java.
Sany Liew
2
"nicht häufig verwendet" == schlechte Praxis? Ich suchte nach genau dem gleichen Verhalten und versuchte, NVI zu implementieren. Und NVI scheint mir eine gute Praxis zu sein.
Saskia
5
Es ist erwähnenswert, dass A :: f () rein zu machen bedeutet, dass B f () implementieren muss (andernfalls wäre B abstrakt und nicht instabil). Und als @MichaelBurr darlegt, eine Implementierung für eine Bereitstellung :: f () bedeutet , dass B kann es f definieren () verwenden.
Fearless_fool
2
IIRC, Scot Meyer hat einen ausgezeichneten Artikel über den Anwendungsfall dieser Frage in einem seiner klassischen Bücher "Effective C ++"
Irsis
75

Um klar zu sein, verstehen Sie falsch, was = 0; nach einer virtuellen Funktion bedeutet.

= 0 bedeutet, dass abgeleitete Klassen eine Implementierung bereitstellen müssen, nicht dass die Basisklasse keine Implementierung bereitstellen kann.

Wenn Sie eine virtuelle Funktion als rein (= 0) markieren, macht es in der Praxis wenig Sinn, eine Definition anzugeben, da sie nur aufgerufen wird, wenn dies ausdrücklich über Base :: Function (...) geschieht oder wenn die Der Basisklassenkonstruktor ruft die betreffende virtuelle Funktion auf.

Terry Mahaffey
quelle
9
Das ist falsch. Wenn Sie diese rein virtuelle Funktion beim Konstruktor Ihrer rein virtuellen Klasse aufrufen, wird ein rein virtueller Aufruf ausgeführt. In diesem Fall haben Sie besser eine Implementierung.
rmn
@rmn, Ja, Sie haben Recht mit virtuellen Aufrufen in Konstruktoren. Ich habe die Antwort aktualisiert. Hoffentlich weiß jeder, dass er das nicht tun soll. :)
Terry Mahaffey
3
Tatsächlich führt ein reiner Basisaufruf von einem Konstruktor zu einem implementierungsdefinierten Verhalten. In VC ++ entspricht dies einem _purecall-Absturz.
Ofek Shilon
@OfekShilon das ist richtig - ich wäre versucht, es auch als undefiniertes Verhalten und als Kandidaten für schlechte Praktiken / Code-Refactoring (dh das Aufrufen virtueller Methoden innerhalb des Konstruktors) zu bezeichnen. Ich denke, es hat mit der Kohärenz der virtuellen Tabelle zu tun, die möglicherweise nicht bereit ist, zum Hauptteil der korrekten Implementierung zu gelangen.
Teodron
1
In Konstruktoren und Destruktoren sind virtuelle Funktionen nicht virtuell.
Jesper Juhl
20

Der Vorteil besteht darin, dass abgeleitete Typen gezwungen werden, die Methode weiterhin zu überschreiben, aber auch eine Standard- oder additive Implementierung bereitgestellt werden.

JaredPar
quelle
1
Warum sollte ich erzwingen wollen, wenn es eine Standardimplementierung gibt? Das klingt nach normalen virtuellen Funktionen. Wenn es sich nur um eine normale virtuelle Funktion handelt, kann ich sie entweder überschreiben. Wenn dies nicht der Fall ist, wird eine Standardimplementierung bereitgestellt (Basisimplementierung).
StackExchange123
19

Wenn Sie Code haben, der von der ableitenden Klasse ausgeführt werden soll, aber nicht direkt ausgeführt werden soll - und Sie möchten, dass er überschrieben wird.

Ihr Code ist korrekt, obwohl dies insgesamt keine häufig verwendete Funktion ist und normalerweise nur angezeigt wird, wenn Sie versuchen, einen reinen virtuellen Destruktor zu definieren. In diesem Fall müssen Sie eine Implementierung bereitstellen. Das Lustige ist, dass Sie, sobald Sie von dieser Klasse abgeleitet sind, den Destruktor nicht mehr überschreiben müssen.

Daher ist die einzig sinnvolle Verwendung von reinen virtuellen Funktionen die Angabe eines reinen virtuellen Destruktors als "nicht endgültiges" Schlüsselwort.

Der folgende Code ist überraschend korrekt:

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

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}
Kornel Kisielewicz
quelle
1
Basisklassen-Destruktoren werden sowieso immer virtuell oder nicht und rein oder nicht aufgerufen. Bei anderen Funktionen können Sie nicht garantieren, dass eine überschreibende virtuelle Funktion die Basisklassenimplementierung aufruft, unabhängig davon, ob die Basisklassenversion rein ist oder nicht.
CB Bailey
1
Dieser Code ist falsch. Sie müssen den dtor aufgrund einer Syntax-Eigenart der Sprache außerhalb der Klassendefinition definieren.
@ Roger: Danke, das hat mir tatsächlich geholfen - dies ist der Code, den ich verwendet habe. Er lässt sich unter MSVC gut kompilieren, aber ich denke, er wäre nicht portabel.
Kornel Kisielewicz
4

Ja das ist korrekt. In Ihrem Beispiel erben Klassen, die von A abgeleitet sind, sowohl die Schnittstelle f () als auch eine Standardimplementierung. Sie erzwingen jedoch abgeleitete Klassen, um die Methode f () zu implementieren (auch wenn nur die von A bereitgestellte Standardimplementierung aufgerufen werden soll).

Scott Meyers diskutiert dies in Effective C ++ (2. Ausgabe) Punkt 36 Unterscheiden Sie zwischen der Vererbung der Schnittstelle und der Vererbung der Implementierung. Die Artikelnummer hat sich möglicherweise in der letzten Ausgabe geändert.

Yukiko
quelle
4

Reine virtuelle Funktionen mit oder ohne Körper bedeuten einfach, dass die abgeleiteten Typen ihre eigene Implementierung bereitstellen müssen.

Reine virtuelle Funktionskörper in der Basisklasse sind nützlich, wenn Ihre abgeleiteten Klassen Ihre Basisklassenimplementierung aufrufen möchten.

Brian R. Bondy
quelle
2

Die 'virtuelle Leere foo () = 0;' Syntax bedeutet nicht, dass Sie foo () nicht in der aktuellen Klasse implementieren können. Dies bedeutet auch nicht, dass Sie es in abgeleiteten Klassen implementieren müssen . Bevor Sie mich schlagen, lassen Sie uns das Diamantproblem beobachten: (Impliziter Code, wohlgemerkt).

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;
}

Der Aufruf von obj-> foo () führt nun zu B :: foo () und dann zu C :: bar ().

Sie sehen ... reine virtuelle Methoden müssen nicht in abgeleiteten Klassen implementiert werden (foo () hat keine Implementierung in Klasse C - Compiler wird kompiliert) In C ++ gibt es viele Lücken.

Hoffe ich konnte helfen :-)

Nir Hedvat
quelle
5
Es muss nicht in ALLEN abgeleiteten Klassen implementiert werden, MUSS jedoch in allen abgeleiteten Klassen implementiert sein, die Sie instanziieren möchten. Sie können Cin Ihrem Beispiel kein Objekt vom Typ instanziieren . Sie können ein Objekt vom Typ instanziiert , Dweil es seine Umsetzung wird fooaus B.
YoungJohn
0

Ein wichtiger Anwendungsfall für eine rein virtuelle Methode mit einem Implementierungskörper ist, wenn Sie eine abstrakte Klasse haben möchten, aber keine geeigneten Methoden in der Klasse haben, um sie rein virtuell zu machen. In diesem Fall können Sie den Destruktor der Klasse rein virtuell machen und dafür die gewünschte Implementierung (sogar einen leeren Körper) einfügen. Als Beispiel:

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

Foo::~Foo()
{
}

Diese Technik macht die FooKlasse abstrakt und daher unmöglich, die Klasse direkt zu instanziieren. Gleichzeitig haben Sie keine zusätzliche rein virtuelle Methode hinzugefügt, um die FooKlasse abstrakt zu machen .

Gupta
quelle