HOWTO · C++
Funktionsüberladung VS-Überschreibung in C++
Dieser Artikel vergleicht Funktionsüberladung mit dem Überschreiben von Funktionen in C++.
Auf dieser Seite
Funktionsüberladung und das Überschreiben von Funktionen lösen in C++ unterschiedliche Aufgaben. Bei der Funktionsüberladung erhalten Funktionen im selben Gültigkeitsbereich denselben Namen, aber unterschiedliche Parameterlisten; der Compiler wählt anhand der Argumente des Aufrufs die passende Funktion aus. Beim Überschreiben von Funktionen ersetzt eine abgeleitete Klasse eine geerbte virtual-Funktion durch eine passende Implementierung. Ein Aufruf über eine Referenz oder einen Zeiger auf die Basisklasse wird zur Laufzeit an das abgeleitete Objekt weitergeleitet.
Was Funktionsüberladung und das Überschreiben von Funktionen in C++ bedeuten
Bei der Funktionsüberladung wählt der Compiler zwischen Funktionen, die sich durch ihre Parameter unterscheiden. Beim Überschreiben von Funktionen wird Verhalten innerhalb einer Vererbungshierarchie ersetzt. Der Rückgabetyp allein unterscheidet keine überladene Funktion, und für das Überschreiben muss die Funktion der Basisklasse virtual sein. Der Spezifizierer override lässt den Compiler prüfen, ob die Deklaration der abgeleiteten Klasse tatsächlich zu einer virtuellen Funktion der Basisklasse passt.
So funktioniert die Funktionsüberladung
Deklarieren Sie Funktionen mit demselben Namen im selben Gültigkeitsbereich und verwenden Sie unterschiedliche Parameterlisten. Diese können sich in der Anzahl oder den Typen der Parameter unterscheiden. Beim Aufruf vergleicht die Überladungsauflösung die Argumente mit den Parametertypen der möglichen Funktionen. Der Rückgabetyp wird nicht zur Auswahl herangezogen. Zwei Deklarationen, die sich nur im Rückgabetyp unterscheiden, bilden daher keine gültige Überladungsmenge.
Führen Sie dieses Beispiel aus:
Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.
#include <iostream>
class Formatter {
public:
void show(int value) const {
std::cout << "integer: " << value << '\n';
}
void show(double value) const {
std::cout << "decimal: " << value << '\n';
}
};
int main() {
Formatter formatter;
formatter.show(7);
formatter.show(2.5);
}
integer: 7
decimal: 2.5
Die bereitgestellten Laufzeitnachweise bestätigen, dass dieses Beispiel erfolgreich ausgeführt wurde und die angezeigte Ausgabe erzeugt hat. Die ganzzahligen und dezimalen Argumente wählen unterschiedliche überladene Varianten von show aus. Achten Sie beim Hinzufügen einer Überladung darauf, dass sich ihre Parameterliste sinnvoll unterscheidet und Aufrufe eindeutig bleiben.
So funktioniert das Überschreiben von Funktionen
Eine Basisklasse deklariert eine Funktion als virtual; eine abgeleitete Klasse stellt eine Funktion mit passendem Namen und passender Parameterliste sowie kompatiblen Qualifizierern und Rückgabetyp bereit. Kennzeichnen Sie die Deklaration in der abgeleiteten Klasse mit override, damit der Compiler Abweichungen meldet. Wird die Funktion über eine Referenz oder einen Zeiger auf die Basisklasse aufgerufen, wählt die virtuelle Dispatch zur Laufzeit die endgültige Überschreibung für das jeweilige Objekt aus.
Führen Sie dieses Beispiel aus:
Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.
#include <iostream>
class Message {
public:
virtual void print() const {
std::cout << "message\n";
}
};
class Alert : public Message {
public:
void print() const override {
std::cout << "alert\n";
}
};
int main() {
Alert alert;
const Message& message = alert;
message.print();
}
alert
Die bereitgestellten Laufzeitnachweise bestätigen, dass dieses Beispiel erfolgreich ausgeführt wurde und die angezeigte Ausgabe erzeugt hat. Obwohl message den Basistyp hat, verweist es auf ein Alert-Objekt. Deshalb ruft der virtuelle Aufruf Alert::print auf. Eine Basisklasse kann außerdem eine rein virtuelle Funktion mit = 0 deklarieren. Eine solche Klasse ist abstrakt und kann nicht instanziiert werden, solange konkrete abgeleitete Klassen ihre rein virtuellen Funktionen nicht implementieren.
Funktionsüberladung und Überschreiben von Funktionen: die wichtigsten Unterschiede
| Aspekt | Funktionsüberladung | Überschreiben von Funktionen |
|---|---|---|
| Zweck | Gleichnamige Operationen für unterschiedliche Argumente bereitstellen | Geerbtes Verhalten für einen abgeleiteten Typ spezialisieren |
| Voraussetzung | Unterschiedliche Parameterlisten im selben Gültigkeitsbereich | Vererbung und eine passende virtual-Funktion in der Basisklasse |
| Auswahl | Überladungsauflösung zur Compilezeit anhand der Aufrufargumente | Virtuelle Dispatch zur Laufzeit über eine Referenz oder einen Zeiger auf die Basisklasse |
| Nützliche Prüfung | Sicherstellen, dass Aufrufe die gewünschte Überladung erkennen lassen | override hinzufügen, um die Übereinstimmung der Deklaration prüfen zu lassen |
Überladungen sind auch ohne Vererbung oder virtuelle Funktionen möglich. Überschreibungen setzen Vererbung und virtuelle Dispatch voraus; sie werden nicht durch eine andere Argumentliste ausgewählt.
Häufige Fehler und wie Sie sie erkennen
Eine gleichnamige Funktion in einer abgeleiteten Klasse überschreibt nicht automatisch eine Funktion der Basisklasse. Ist die Funktion der Basisklasse nicht virtual, verdeckt die Deklaration in der abgeleiteten Klasse den Namen der Basisklassenfunktion, statt virtuelle Dispatch zu ermöglichen. Stimmen ein Parameter, der const-Qualifizierer oder ein anderer Teil des Funktionstyps nicht überein, kann die beabsichtigte Überschreibung scheitern. Mit override macht der Compiler diese Abweichung bereits zur Compilezeit sichtbar.
Versuchen Sie nicht, eine Überladung allein durch Änderung des Rückgabetyps zu erzeugen. Übergeben Sie abgeleitete Objekte auch nicht als Wert, wenn Sie polymorphes Verhalten erwarten: Beim Kopieren in ein Objekt der Basisklasse geht der abgeleitete Teil verloren. Verwenden Sie eine Referenz oder einen Zeiger auf die Basisklasse, um dynamische Dispatch zu erhalten. Für gewöhnliche Besitzverwaltung sind automatische Objekte oder intelligente Zeiger vorzuziehen. Wird ein abgeleitetes Objekt über einen Zeiger auf die Basisklasse gelöscht, sollte die Basisklasse einen virtuellen Destruktor haben.