C++-Programmierung/ Eigene Datentypen definieren/ Überladen…
Aus Wikibooks
Sie haben bereits gelernt, dass Funktionen überladen werden können, indem für den gleichen Funktionsnamen mehrere Deklarationen mit verschiedenen Parametern gemacht werden. Auch bei Klassenkonstruktoren haben Sie Überladung bereits kennengelernt. Für gewöhnliche Memberfunktionen ist eine Überladung ebenfalls nach den Ihnen bereits bekannten Kriterien möglich. Zusätzlich können Sie Memberfunktionen aber anhand des eben vorgestellten const-Modifizierers überladen.
public:
void methode(); // Eine Methode
void methode()const; // Die überladene Version der Methode für konstante Objekte
};
Natürlich können auch Methoden mit Parametern auf diese Weise überladen werden. Die nicht-konstante Version wird immer dann aufgerufen, wenn Sie mit einem nicht-konstanten Objekt der Klasse arbeiten. Analog dazu wird die konstante Version aufgerufen, wenn Sie mit einem konstanten Objekt arbeiten. Wenn Sie nur eine konstante Version deklarieren, wird immer diese aufgerufen.
Sinnvoll ist diese Art der Überladung vor allem dann, wenn Sie einen Zeiger oder eine Referenz auf etwas innerhalb des Objekts zurückgeben. Wie Sie sich sicher erinnern, kann eine Überladung nicht anhand des Rückgabetyps einer Funktion (oder Methode) gemacht werden. Das folgende Beispiel wird Ihnen zeigen, wie Sie eine const-Überladung nutzen können, um direkte Manipulation von Daten innerhalb eines Objekts nur für nicht-konstante Objekte zulassen.
class A{
public:
A():m_b(7) {} // m_b mit 7 initialisieren
int& B() { return m_b; } // Zugriff lesend und schreibend
int const& B()const { return m_b; } // Zugriff nur lesend
private:
int m_b; // Daten
};
int main(){
A objekt; // Ein Objekt von A
A const objekt_const; // Ein konstantes Objekt von A
std::cout << objekt.B() << std::endl; // Gibt 7 aus
std::cout << objekt_const.B() << std::endl; // Gibt 7 aus
objekt.B() = 9; // setzt den Wert von m_b auf 9
// objekt_const.B() = 9; // Produziert einen Kompilierfehler
std::cout << objekt.B() << std::endl; // Gibt 9 aus
std::cout << objekt_const.B() << std::endl; // Gibt 7 aus
}
Im Kapitel über Operatorüberladung werden Sie noch ein Beispiel zu dieser Technik kennenlernen, welches in der Praxis oft zu sehen ist.
[Bearbeiten] Code-Verdopplung vermeiden
Im Beispiel von eben geben die beiden Funktionen lediglich eine Referenz auf eine Membervariable innerhalb des Objekts zurück. In der Regel wird eine solche Funktionen natürlich noch etwas mehr tun. Daher wäre es nötig, zwei Funktionen zu schreiben, die den gleichen Code enthalten. Das wiederum ist ausgesprochen schlechter Stil. Stellen Sie sich nur vor, Sie möchten die Funktion später aus irgendwelchen Gründen ändern, dann müssten Sie alle Änderungen an zwei Stellen im Code vornehmen.
Daher ist es sinnvoll, wenn eine Variante die andere aufruft. Hiefür sind einige Tricks nötig, da Sie einer der beiden Varianten beibringen müssen, eine Methode aufzurufen, die eigentlich nicht zum const-Modifizierer des aktuellen Objekts passt. Die konstante Variante verspricht niemals eine Änderung am Objekt vorzunehmen, sie ist in ihren Möglichkeiten also stärker eingeschränkt. Die nicht konstante Version darf hingegen alles, was auch die konstante Version darf. Somit ist es sinnvoll, die konstante Version von der nicht-konstanten aufrufen zu lassen.
Um nun der nicht-konstanten Version beizubringen, dass sie ihr konstantes Äquivalent aufrufen soll, müssen wir zunächst einmal aus dem aktuellen Objekt ein konstantes Objekt machen. Jede Klasse enthält eine spezielle Variable, den sogenannten this-Zeiger, der innerhalb einer Membervariable einen Zeiger auf das aktuelle Objekt repräsentiert. Diesen this-Zeiger casten wir in einen Zeiger auf ein konstantes Objekt.
int A::B(){
A const* objekt_const = static_cast< A const* >(this); // Konstantheit dazu casten
Nun haben wir einen Zeiger auf das Objekt, über den wir nur konstante Methoden aufrufen können. Das Problem ist nun, dass die aufgerufene Methode natürlich auch eine Referenz auf eine konstante Variable aufruft.
Da wir ja wissen, dass das aktuelle Objekt eigentlich gar nicht konstant ist, können wir die Konstantheit für die zurückgegebene Referenz guten Gewissens entfernen. Allerdings ist der static_cast, den Sie bereits kennen, nicht dazu in der Lage. Um Konstantheit zu entfernen, benötigen Sie den const_cast. Beachten Sie jedoch, dass dieser Cast wirklich nur auf Variablen angewendet werden darf, die eigentlich nicht konstant sind!
Wenn Sie diese Anweisungen in einer Zusammenfassen, sieht Ihre Klasse nun folgendermaßen aus.
public:
// m_b mit 7 initialisieren
A():m_b(7) {}
// Ruft B()const auf
int& B() { return const_cast< int& >( static_cast< A const* >(this)->B() ); }
int const& B()const { return m_b; }
private:
int m_b; // Daten
};
Wie schon gesagt, sieht diese Technik in unserem Beispiel überdimensioniert aus. Aber auch an dieser Stelle möchte ich Sie auf das Beispiel im Kapitel zur Operatorüberladung verweisen, wo sie, aufgrund der umfangreicheren konstanten Version, bereits deutlich angenehmer erscheint. Mit Performanceeinbußen haben Sie an dieser Stelle übrigens nicht zu rechnen. Ihr Compiler wird die casting-Operationen wegoptimieren.