webentwicklung-frage-antwort-db.com.de

C ++ "virtuelles" Schlüsselwort für Funktionen in abgeleiteten Klassen. Ist es nötig?

Mit der unten angegebenen Strukturdefinition ...

struct A {
    virtual void hello() = 0;
};

Ansatz 1:

struct B : public A {
    virtual void hello() { ... }
};

Ansatz 2:

struct B : public A {
    void hello() { ... }
};

Gibt es einen Unterschied zwischen diesen beiden Möglichkeiten, um die Hallo-Funktion zu überschreiben?

207
Anarki

Sie sind genau das gleiche. Es gibt keinen Unterschied zwischen ihnen, außer dass der erste Ansatz mehr Eingaben erfordert und potenziell klarer ist.

167
James McNellis

Die 'Virtualität' einer Funktion wird implizit weitergegeben. Mindestens ein Compiler, den ich verwende, generiert jedoch eine Warnung, wenn das Schlüsselwort virtual nicht explizit verwendet wird. Sie können es also verwenden, wenn Sie nur den Compiler ruhig halten möchten .

Unter rein stilistischen Gesichtspunkten, einschließlich des Schlüsselworts virtual, wird dem Benutzer die Tatsache klar angezeigt, dass die Funktion virtuell ist. Dies ist wichtig für alle, die B weiter unterteilen, ohne die Definition von A überprüfen zu müssen. Für tiefe Klassenhierarchien wird dies besonders wichtig.

81
Clifford

Das Schlüsselwort virtual ist in der abgeleiteten Klasse nicht erforderlich. Hier ist die unterstützende Dokumentation aus dem C++ Draft Standard (N3337) (Schwerpunkt Mine):

10.3 Virtuelle Funktionen

2 Wenn eine virtuelle Mitgliedsfunktion vf in einer Klasse Base und in einer Klasse Derived deklariert ist, die direkt oder indirekt von Base abgeleitet ist, wird eine Mitgliedsfunktion vf mit demselben Namen, Parameter-Typ-Liste (8.3.5), Lebenslauf-Qualifikation und Referenz-Qualifikation (oder Fehlen derselben) wie Base::vf wird deklariert, dann Derived::vf ist auch virtuell (ob es so deklariert ist oder nicht) und überschreibt Base::vf.

49
R Sahu

Nein, das Schlüsselwort virtual für das Überschreiben der virtuellen Funktionen abgeleiteter Klassen ist nicht erforderlich. Es ist jedoch erwähnenswert, dass eine virtuelle Funktion nicht überschrieben werden kann.

Der Fehler beim Überschreiben tritt auf, wenn Sie beabsichtigen, eine virtuelle Funktion in einer abgeleiteten Klasse zu überschreiben, jedoch einen Fehler in der Signatur machen, sodass ein neues und deklariert wird andere virtuelle Funktion. Diese Funktion kann eine Überladung der Basisklassenfunktion sein oder sich im Namen unterscheiden. Unabhängig davon, ob Sie das Schlüsselwort virtual in der Funktionsdeklaration der abgeleiteten Klasse verwenden oder nicht, kann der Compiler nicht erkennen, dass Sie beabsichtigen, eine Funktion einer Basisklasse zu überschreiben.

Diese Gefahr wird jedoch dankenswerterweise durch die Sprachfunktion C++ 11 explizites Überschreiben behoben, mit der der Quellcode eindeutig angeben kann, dass eine Member-Funktion eine Basisklassenfunktion überschreiben soll:

struct Base {
    virtual void some_func(float);
};

struct Derived : Base {
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};

Der Compiler gibt einen Fehler beim Kompilieren aus, und der Programmierfehler ist sofort offensichtlich (möglicherweise hätte die Funktion in Derived ein float als Argument verwenden sollen).

Siehe WP: C++ 11 .

31
Colin D Bennett

Das Hinzufügen des Schlüsselworts "virtual" ist empfehlenswert, da es die Lesbarkeit verbessert, jedoch nicht erforderlich ist. Funktionen, die in der Basisklasse als virtuell deklariert wurden und in den abgeleiteten Klassen dieselbe Signatur aufweisen, werden standardmäßig als "virtuell" betrachtet.

11
Sujay Ghosh

Es gibt keinen Unterschied für den Compiler, wenn Sie virtual in die abgeleitete Klasse schreiben oder weglassen.

Sie müssen sich jedoch die Basisklasse ansehen, um diese Informationen zu erhalten. Daher würde ich empfehlen, das Schlüsselwort virtual auch in der abgeleiteten Klasse hinzuzufügen, wenn Sie dem Menschen zeigen möchten, dass diese Funktion virtuell ist.

7
harper

Es gibt einen erheblichen Unterschied, wenn Sie Vorlagen haben und anfangen, Basisklassen als Vorlagenparameter zu verwenden:

struct None {};

template<typename... Interfaces>
struct B : public Interfaces
{
    void hello() { ... }
};

struct A {
    virtual void hello() = 0;
};

template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
    b.hello();   // indirect, non-virtual call
}

void hello(const A& a)
{
    a.hello();   // Indirect virtual call, inlining is impossible in general
}

int main()
{
    B<None>  b;         // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
    B<None>* pb = &b;
    B<None>& rb = b;

    b.hello();          // direct call
    pb->hello();        // pb-relative non-virtual call (1 redirection)
    rb->hello();        // non-virtual call (1 redirection unless optimized out)
    t_hello(b);         // works as expected, one redirection
    // hello(b);        // compile-time error


    B<A>     ba;        // Ok, vtable generated, sizeof(b) >= sizeof(void*)
    B<None>* pba = &ba;
    B<None>& rba = ba;

    ba.hello();         // still can be a direct call, exact type of ba is deducible
    pba->hello();       // pba-relative virtual call (usually 3 redirections)
    rba->hello();       // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
    //t_hello(b);       // compile-time error (unless you add support for const A& in t_hello as well)
    hello(ba);
}

Das Schöne daran ist, dass Sie jetzt Interface- und Nicht-Interface-Funktionen definieren können später um Klassen zu definieren. Dies ist nützlich für die Zusammenarbeit von Schnittstellen zwischen Bibliotheken (verlassen Sie sich nicht auf diesen Standardentwurfsprozess für eine einzelne Bibliothek). Es kostet Sie nichts, dies für alle Ihre Klassen zuzulassen - Sie können sogar typedef B für etwas festlegen, wenn Sie möchten.

Beachten Sie, dass Sie in diesem Fall möglicherweise auch Kopier-/Verschiebungskonstruktoren als Vorlagen deklarieren möchten: Wenn Sie Konstruktionen aus verschiedenen Schnittstellen erstellen, können Sie zwischen verschiedenen B<> - Typen 'umwandeln'.

Es ist fraglich, ob Sie in t_hello() Unterstützung für const A& Hinzufügen sollten. Der übliche Grund für diese Umschreibung ist die Abkehr von einer vererbungsbasierten zu einer vorlagenbasierten Spezialisierung, hauptsächlich aus Gründen der Leistung. Wenn Sie die alte Schnittstelle weiterhin unterstützen, können Sie eine alte Verwendung kaum erkennen (oder von ihr abhalten).

1
lorro

Das Schlüsselwort virtual sollte zu Funktionen einer Basisklasse hinzugefügt werden, um sie überschreibbar zu machen. In Ihrem Beispiel ist struct A ist die Basisklasse. virtual bedeutet nichts für die Verwendung dieser Funktionen in einer abgeleiteten Klasse. Wenn Sie jedoch möchten, dass Ihre abgeleitete Klasse auch selbst eine Basisklasse ist und diese Funktion überschrieben werden soll, müssen Sie virtual dort ablegen.

struct B : public A {
    virtual void hello() { ... }
};

struct C : public B {
    void hello() { ... }
};

Hier erbt C von B, daher ist B nicht die Basisklasse (es ist auch eine abgeleitete Klasse), und C ist die abgeleitete Klasse. Das Vererbungsdiagramm sieht folgendermaßen aus:

A
^
|
B
^
|
C

Stellen Sie also virtual vor Funktionen in potenzielle Basisklassen, die möglicherweise untergeordnete Klassen haben. Mit virtual können Ihre Kinder Ihre Funktionen überschreiben. Es ist nichts Falsches daran, das virtual vor Funktionen in den abgeleiteten Klassen zu setzen, aber es ist nicht erforderlich. Es wird jedoch empfohlen, da jemand, der von Ihrer abgeleiteten Klasse erben möchte, sich nicht darüber freut, dass das Überschreiben der Methode nicht wie erwartet funktioniert.

Stellen Sie also virtual in allen an der Vererbung beteiligten Klassen vor Funktionen, es sei denn, Sie wissen, dass die Klasse keine untergeordneten Klassen hat, die die Funktionen der Basisklasse überschreiben müssten. Es ist eine gute Praxis.

1
Galaxy

Ich werde auf jeden Fall das Schlüsselwort Virtual für die untergeordnete Klasse einfügen, weil

  • ich. Lesbarkeit.
  • ii. Diese untergeordnete Klasse kann weiter unten abgeleitet werden. Sie möchten nicht, dass der Konstruktor der weiter abgeleiteten Klasse diese virtuelle Funktion aufruft.
0
user2264698