Überschreiben Sie virtuelle C ++ - Funktionen sicher

100

Ich habe eine Basisklasse mit einer virtuellen Funktion und möchte diese Funktion in einer abgeleiteten Klasse überschreiben. Gibt es eine Möglichkeit, den Compiler überprüfen zu lassen, ob die in der abgeleiteten Klasse deklarierte Funktion tatsächlich eine Funktion in der Basisklasse überschreibt? Ich möchte ein Makro oder etwas hinzufügen, das sicherstellt, dass ich nicht versehentlich eine neue Funktion deklariert habe, anstatt die alte zu überschreiben.

Nehmen Sie dieses Beispiel:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Hier parent::handle_event()wird statt aufgerufen child::handle_event(), weil die Methode des Kindes die constDeklaration verfehlt und daher eine neue Methode deklariert. Dies kann auch ein Tippfehler im Funktionsnamen oder ein geringfügiger Unterschied in den Parametertypen sein. Es kann auch leicht vorkommen, dass sich die Schnittstelle der Basisklasse ändert und irgendwo eine abgeleitete Klasse nicht aktualisiert wurde, um die Änderung widerzuspiegeln.

Gibt es eine Möglichkeit, dieses Problem zu vermeiden? Kann ich dem Compiler oder einem anderen Tool irgendwie sagen, dass er dies für mich überprüfen soll? Irgendwelche hilfreichen Compiler-Flags (vorzugsweise für g ++)? Wie vermeiden Sie diese Probleme?

etw
quelle
2
Gute Frage, ich habe versucht herauszufinden, warum meine Kinderklassenfunktion seit einer Stunde nicht mehr aufgerufen wird!
Akash Mankar

Antworten:

89

Seit g ++ 4.7 versteht es das neue C ++ 11- overrideSchlüsselwort:

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};
Gunther Piez
quelle
@hirschhornsalz: Ich habe festgestellt, dass g ++ beim Implementieren der Funktion handle_event und beim Anhängen der Überschreibung am Ende der Funktionsimplementierung einen Fehler ausgibt. Wenn Sie in der Klassendeklaration nach dem Schlüsselwort override eine Inline-Funktionsimplementierung bereitstellen, ist alles in Ordnung. Warum?
h9uest
3
@ h9uest overridemuss in der Definition verwendet werden. Eine Inline-Implementierung ist sowohl Definition als auch Implementierung, das ist also in Ordnung.
Gunther Piez
@hirschhornsalz Ja, ich habe die gleiche Fehlermeldung von g ++. Eine Randnotiz: Sowohl Sie als auch g ++ error msg haben den Begriff "Klassendefinition" verwendet - sollten wir nicht "Deklaration" ({Deklaration, Definition} Paar) verwenden? Sie haben sich in diesem speziellen Kontext mit "Definition & Implementierung" klargestellt, aber ich frage mich nur, warum die C ++ - Community plötzlich beschließt, die Begriffe für Klassen zu ändern.
h9uest
20

So etwas wie das overrideSchlüsselwort von C # ist nicht Teil von C ++.

Warnt in gcc -Woverloaded-virtualdavor, eine virtuelle Basisklassenfunktion mit einer Funktion mit demselben Namen, aber einer ausreichend unterschiedlichen Signatur auszublenden, damit sie nicht überschrieben wird. Es schützt Sie jedoch nicht davor, eine Funktion nicht zu überschreiben, weil der Funktionsname selbst falsch geschrieben wurde.

CB Bailey
quelle
2
Es ist, wenn Sie zufällig Visual C ++
Steve Rowe
4
Bei Verwendung von Visual C ++ wird overridein C ++ kein Schlüsselwort erstellt. Dies kann jedoch bedeuten, dass Sie etwas verwenden, das ungültigen C ++ - Quellcode kompilieren kann. ;)
CB Bailey
3
Die Tatsache, dass das Überschreiben ungültig ist, bedeutet, dass der Standard falsch ist und nicht Visual C ++
Jon
2
@ Jon: OK, jetzt sehe ich, worauf du gefahren bist. Persönlich könnte ich overrideFunktionen im C # -Stil übernehmen oder verlassen . Ich hatte selten Probleme mit fehlgeschlagenen Überschreibungen und sie waren relativ einfach zu diagnostizieren und zu beheben. Ein Gedanke, dem ich nicht zustimmen werde, ist, dass VC ++ - Benutzer es verwenden sollten. Ich würde es vorziehen, wenn C ++ auf allen Plattformen wie C ++ aussieht, auch wenn ein bestimmtes Projekt nicht portabel sein muss. Es ist erwähnenswert, dass C ++ 0x haben wird [[base_check]], [[override]]und [[hiding]]Attribute , so dass Sie kann in entscheiden Kontrolle außer Kraft zu setzen , falls gewünscht.
CB Bailey
5
Komischerweise macht ein paar Jahre nach dem Kommentar mit VC ++override anscheinend kein Schlüsselwort aus . Nun, kein richtiges Schlüsselwort, sondern eine spezielle Kennung in C ++ 11. Microsoft hat sich genug Mühe gegeben, um einen Sonderfall daraus zu machen und dem allgemeinen Format für Attribute zu folgen und overridees zum Standard zu machen :)
David Rodríguez - dribeas
18

Kannst du es, soweit ich weiß, nicht einfach abstrakt machen?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Ich dachte, ich hätte auf www.parashift.com gelesen, dass man tatsächlich eine abstrakte Methode implementieren kann. Was für mich persönlich Sinn macht, das einzige, was es tut, ist, Unterklassen zu zwingen, es zu implementieren. Niemand hat etwas darüber gesagt, dass es nicht erlaubt ist, selbst eine Implementierung zu haben.

Ray Hidayat
quelle
Erst jetzt habe ich festgestellt, dass dies nicht nur bei Destruktoren funktioniert! Toller Fund.
Strager
1
Dies hat einige mögliche Nachteile: 1) Wenn Sie eine oder mehrere Methoden als abstrakt markieren, wird die Basisklasse nicht instanziierbar. Dies kann ein Problem sein, wenn dies nicht Teil der beabsichtigten Verwendung der Klasse ist. 2) Die Basisklasse kann möglicherweise nicht von Ihnen geändert werden.
Michael Burr
3
Ich stimme Michael Burr zu. Es ist nicht Teil der Frage, die Basisklasse abstrakt zu machen. Es ist durchaus sinnvoll, eine Basisklasse mit Funktionen in einer virtuellen Methode zu haben, die von einer abgeleiteten Klasse überschrieben werden soll. Genauso vernünftig ist es, sich vor einem anderen Programmierer schützen zu wollen, der die Funktion in der Basisklasse umbenennt und bewirkt, dass Ihre abgeleitete Klasse sie nicht mehr überschreibt. Die Microsoft-Erweiterung "override" ist in diesem Fall von unschätzbarem Wert. Ich würde gerne sehen, dass es zum Standard hinzugefügt wird, da es leider keinen guten Weg gibt, dies ohne es zu tun.
Brian
Dies verhindert auch, dass die Basismethode (say BaseClass::method()) in der abgeleiteten Implementierung (say DerivedClass::method()) aufgerufen wird , z. B. für einen Standardwert.
Narcolessico
11

In MSVC können Sie das overrideSchlüsselwort CLR verwenden, auch wenn Sie nicht für CLR kompilieren.

In g ++ gibt es keine direkte Möglichkeit, dies in allen Fällen durchzusetzen. andere Leute haben gute Antworten gegeben, wie man Signaturunterschiede mit erkennt -Woverloaded-virtual. In einer zukünftigen Version könnte jemand eine Syntax wie __attribute__ ((override))oder eine gleichwertige mithilfe der C ++ 0x-Syntax hinzufügen .

Doug
quelle
9

In MSVC ++ können Sie Schlüsselwörter verwendenoverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override funktioniert sowohl für nativen als auch für CLR-Code in MSVC ++.

Bobobobo
quelle
5

Machen Sie die Funktion abstrakt, damit abgeleitete Klassen keine andere Wahl haben, als sie zu überschreiben.

@ Ray Dein Code ist ungültig.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Bei abstrakten Funktionen können keine Körper inline definiert werden. Es muss geändert werden, um zu werden

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }
Tanveer Badar
quelle
3

Ich würde eine leichte Änderung Ihrer Logik vorschlagen. Es kann funktionieren oder nicht, je nachdem, was Sie erreichen müssen.

handle_event () kann weiterhin den "langweiligen Standardcode" ausführen, aber anstatt virtuell zu sein, muss die Basisklasse an dem Punkt, an dem der "neue aufregende Code" ausgeführt werden soll, eine abstrakte Methode aufrufen (dh muss überschrieben werden) das wird von Ihrer Nachkommenklasse geliefert.

EDIT: Und wenn Sie später entscheiden , dass einige Ihrer abgeleiteten Klassen können nicht brauchen „neuen spannenden Code“ zu schaffen , dann können Sie die abstrakte zu virtuellen ändern und eine leeren Basisklasse Umsetzung dieser „eingefügt“ Funktionalität liefern.

JMD
quelle
2

Ihr Compiler hat möglicherweise eine Warnung, die er generieren kann, wenn eine Basisklassenfunktion ausgeblendet wird. Wenn dies der Fall ist, aktivieren Sie es. Dadurch werden konstante Konflikte und Unterschiede in den Parameterlisten aufgefangen. Leider wird dadurch kein Rechtschreibfehler aufgedeckt.

Dies ist beispielsweise eine Warnung C4263 in Microsoft Visual C ++.

Mark Ransom
quelle
1

overrideWenn das Schlüsselwort C ++ 11 mit der Funktionsdeklaration innerhalb der abgeleiteten Klasse verwendet wird, muss der Compiler überprüfen, ob die deklarierte Funktion tatsächlich eine Basisklassenfunktion überschreibt. Andernfalls gibt der Compiler einen Fehler aus.

Daher können Sie einen overrideBezeichner verwenden, um einen dynamischen Polymorphismus sicherzustellen (Überschreiben von Funktionen).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
Adarsh ​​Kumar
quelle