kennen:spezielle_methoden
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
— | kennen:spezielle_methoden [2020-07-27 09:58] (aktuell) – angelegt - Externe Bearbeitung 127.0.0.1 | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | ====== Spezielle Methoden ====== | ||
+ | > Learn the rules like a pro, so you can break them like an artist. | ||
+ | >>--- Pablo Picasso | ||
+ | |||
+ | ===== Vom Compiler erzeugt ===== | ||
+ | Schon eine leere [[klassen|Klasse]] oder Struktur | ||
+ | <code cpp> | ||
+ | struct Empty {}; | ||
+ | </ | ||
+ | besitzt implizit generierte Methoden zum | ||
+ | Erzeugen, Kopieren, Zuweisen, Verschieben und Vernichten | ||
+ | von [[klassen# | ||
+ | <code cpp> | ||
+ | struct Empty | ||
+ | { | ||
+ | Empty() {} // Standardkonstruktor | ||
+ | Empty(Empty const& rhs) {} // Kopierkonstruktor | ||
+ | Empty(Empty&& | ||
+ | Empty& operator=(Empty const& rhs) { return *this; } // Kopierzuweisung | ||
+ | Empty& operator=(Empty&& | ||
+ | ~Empty() {} // Destruktor | ||
+ | }; | ||
+ | </ | ||
+ | In vielen Klassen ist dieses Standardverhalten ausreichend: | ||
+ | < | ||
+ | struct Punkt { int x, y; }; | ||
+ | |||
+ | void f() | ||
+ | { | ||
+ | Punkt a; // Standardkonstruktor | ||
+ | Punkt b { 1, 2 }; // POD-Konstruktor | ||
+ | Punkt c (a); // Kopierkonstruktor | ||
+ | a = b; // Zuweisung | ||
+ | std:: | ||
+ | } // Destruktor | ||
+ | </ | ||
+ | Die Elemente der Klasse werden erzeugt, kopiert, verschoben und vernichtet, | ||
+ | indem deren spezielle Methoden aufgerufen werden. | ||
+ | Einige Regeln sind leicht einzusehen. | ||
+ | * Das " | ||
+ | * Ein Objekt lässt sich nur dann kopieren bzw. verschieben, | ||
+ | * Einem konstanten Attribut lässt sich kein neuer Wert zuweisen. | ||
+ | * Verbietet man das Vernichten von Objekten, wäre schon deren Erzeugung ein Fehler. | ||
+ | Andere Regeln sind einzuhalten, | ||
+ | und [[# | ||
+ | [[# | ||
+ | |||
+ | ==== default und delete ==== | ||
+ | Die speziellen Methoden können | ||
+ | ab C++11 durch Markieren als '' | ||
+ | anstelle eines Funktionsrumpfes mit dem Standardverhalten realisiert | ||
+ | oder durch die Angabe '' | ||
+ | (siehe [[.: | ||
+ | Vor C++11 konnten Kopie bzw. Zuweisung unmöglich gemacht werden, | ||
+ | indem diese Methoden als '' | ||
+ | |||
+ | |||
+ | ==== Regeln ==== | ||
+ | Die speziellen Methoden können vom Nutzer umdefiniert werden. | ||
+ | Dann werden einige der anderen Methoden nicht mehr automatisch erzeugt: | ||
+ | ^ ^Nutzer definiert ^^^^^^^ | ||
+ | ^ ^nichts ^Konstruktor ^Destruktor ^Kopierkonstruktor^ Zuweisungsoperator^ Verschiebekonstruktor^ Verschiebezuweisung^ | ||
+ | ^Standardkonstruktor | ||
+ | ^Destruktor | ||
+ | ^Kopierkonstruktor | ||
+ | ^Zuweisungsoperator | ||
+ | ^Verschiebekonstruktor | '' | ||
+ | ^Verschiebezuweisung | ||
+ | |||
+ | (!) ... sollte vom Nutzer verboten oder umdefiniert werden | ||
+ | |||
+ | Besitzt eine Klasse einen nichttrivialen [[# | ||
+ | sind zumeist auch [[# | ||
+ | in geeigneter Weise zu implementieren ([[begriffe# | ||
+ | Die in C++11 eingeführte [[# | ||
+ | wird in diesem Fall nicht automatisch erzeugt. Deren Definition durch den Nutzer wiederum unterdrückt das automatische Erzeugen der anderen speziellen Funktionen. | ||
+ | Eigene Versionen davon können das Laufzeitverhalten der Klasse | ||
+ | durch das Vermeiden teurer Kopien verbessern ([[begriffe# | ||
+ | |||
+ | |||
+ | ===== Besitzverhalten ===== | ||
+ | Die Verwaltung von Ressourcen | ||
+ | (dynamischer Speicher, Sperren auf Dateien, Mutexe) wird durch C++ erleichtert, | ||
+ | wenn beim Erschaffen der Ressource ein Objekt angelegt wird, | ||
+ | welches bei seiner Vernichtung im Destruktor diese Ressource automatisch | ||
+ | freigibt ([[.: | ||
+ | Der Besitzer ist für die Freigabe [[: | ||
+ | Bjarne Stroustrup bezeichnet dies als die beste Form von | ||
+ | //garbage collection// | ||
+ | |||
+ | Eine Klasse für n-reihige, quadratische Matrizen soll als Beispiel dienen. | ||
+ | <code cpp> | ||
+ | class Matrix | ||
+ | { | ||
+ | public: | ||
+ | explicit Matrix(int n); | ||
+ | : n(n) | ||
+ | , data(new double[n*n]) | ||
+ | { | ||
+ | std:: | ||
+ | } | ||
+ | |||
+ | Matrix(); | ||
+ | ~Matrix(); | ||
+ | | ||
+ | Matrix(Matrix const& orig); | ||
+ | Matrix& operator=(Matrix const& rhs); | ||
+ | | ||
+ | Matrix(Matrix&& | ||
+ | Matrix& operator=(Matrix&& | ||
+ | | ||
+ | // Elementzugriff, | ||
+ | private: | ||
+ | int n; | ||
+ | double* data; | ||
+ | }; | ||
+ | </ | ||
+ | Je nach Zeilenzahl n wird der Speicher für die Zahlen dynamisch angelegt. | ||
+ | Neben den Rechenoperationen sind die speziellen Methoden zu definieren, | ||
+ | um die Besitzverhältnisse zu regeln. | ||
+ | |||
+ | ==== Destruktor ==== | ||
+ | Die Freigabe des Speichers erfolgt am Ende der Lebensdauer der Matrix: | ||
+ | <code cpp> | ||
+ | Matrix:: | ||
+ | { | ||
+ | delete[] data; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Standardkonstruktor ==== | ||
+ | Eine leere Matrix hat keinen Speicher. | ||
+ | Dennoch muss der Zeiger sinnnvoll gesetzt werden. | ||
+ | <code cpp> | ||
+ | Matrix:: | ||
+ | : n(0) | ||
+ | , data(nullptr) | ||
+ | { | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Kopierkonstruktor ==== | ||
+ | Ein Kopierkonstruktor erzeugt eine Kopie von einem Objekt gleichen Typs. | ||
+ | Im Standardverhalten werden die [[# | ||
+ | Die (flache) Kopie eines Zeigers zeigt auf den gleichen Speicherplatz. | ||
+ | Dieser würde durch die Destruktoren des Originals und der Kopie | ||
+ | zweimalig freigegeben --- mit fatalen Folgen. | ||
+ | Ein nicht-trivialer [[# | ||
+ | zum Freigeben von Ressourcen ist ein verlässlicher Hinweis darauf, | ||
+ | dass Kopierkonstruktor und [[# | ||
+ | oder verboten werden sollten. | ||
+ | |||
+ | Für die Matrix wird eine tiefe Kopie erzeugt:{{ : | ||
+ | <code cpp> | ||
+ | Matrix:: | ||
+ | : n(orig.n) | ||
+ | , data(new double[orig.n*orig.n]) | ||
+ | { | ||
+ | std:: | ||
+ | } | ||
+ | </ | ||
+ | Andere Herangehensweisen wären gemeinsamer Besitz (shared resource) | ||
+ | oder lazy copying (copy on write). | ||
+ | |||
+ | ==== Zuweisungsoperator ==== | ||
+ | {{ : | ||
+ | Die kanonische Implementierung der Kopierzuweisung prüft, | ||
+ | ob überhaupt zugewiesen werden muss, | ||
+ | legt dann ein Kopie des rechten Operanden (rhs = right-hand side) an | ||
+ | und vertauscht dann die Zeiger auf die Inhalte mit der Kopie. | ||
+ | Tritt beim Anlegen der Kopie eine Ausnahme auf, bleibt die Matrix unverändert. | ||
+ | <code cpp> | ||
+ | Matrix& Matrix:: | ||
+ | { | ||
+ | if (this != &rhs) | ||
+ | { | ||
+ | Matrix tmp(rhs); | ||
+ | std:: | ||
+ | std:: | ||
+ | } | ||
+ | return *this; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Verschiebekonstruktor ==== | ||
+ | Beim Verschieben geht der Besitz von Ressourcen auf das neue Objekt über. | ||
+ | Der Verschiebekonstruktor (engl. move constructor) | ||
+ | übernimmt dazu die Elemente eines als [[.: | ||
+ | übernommenen Objekts. | ||
+ | Als nun leere Hülle bekommt die Quelle im Austausch | ||
+ | einen '' | ||
+ | |||
+ | {{ : | ||
+ | <code cpp> | ||
+ | Matrix:: | ||
+ | : n(0) | ||
+ | , data(nullptr) | ||
+ | { | ||
+ | std:: | ||
+ | std:: | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Verschiebezuweisung ==== | ||
+ | Die Verschiebezuweisung (engl. move assignment) | ||
+ | ist dann in ähnlicher Weise zu definieren: | ||
+ | <code cpp> | ||
+ | Matrix& Matrix:: | ||
+ | { | ||
+ | std:: | ||
+ | std:: | ||
+ | return *this; | ||
+ | } | ||
+ | </ | ||
+ | Kopier- und Verschiebezuweisung können in einer Methode zusammengefasst werden | ||
+ | ([[http:// | ||
+ | <code cpp> | ||
+ | Matrix& Matrix:: | ||
+ | { | ||
+ | std:: | ||
+ | std:: | ||
+ | return *this; | ||
+ | } | ||
+ | </ | ||
+ | Hier ist '' | ||
+ | |||
+ | ==== Verschiebesemantik bringt Zeitgewinn ==== | ||
+ | Werden nun noch die fehlenden Rechenoperationen | ||
+ | definiert (Verschiebekonstruktor für '' | ||
+ | und Verschiebung des Rückgabewertes) | ||
+ | <code cpp> | ||
+ | Matrix operator+(Matrix a, Matrix const& b) | ||
+ | { | ||
+ | a += b; | ||
+ | return a; | ||
+ | } | ||
+ | |||
+ | void demo() | ||
+ | { | ||
+ | Matrix a(3000), b(3000); | ||
+ | a(0,0) = a(1,1) = a(2,2) = 1; | ||
+ | b(2,0) = b(1,1) = b(0,2) = 1; | ||
+ | Matrix c = a + b + a; | ||
+ | c = b + a + b; | ||
+ | // ... | ||
+ | } | ||
+ | </ | ||
+ | erfordern die Anweisungen zur Berechnung der Matrix '' | ||
+ | durch die Verschiebesemantik nur noch zwei statt sechs teure Kopien | ||
+ | für die ersten Zwischenergebnisse. | ||
+ | |||
+ | ===== Von Containern bereitgestellt ==== | ||
+ | Das Arbeit mit " | ||
+ | bringt viel Verantwortung mit sich und kann im Ausnahmefall(!) schiefgehen. | ||
+ | [[stl# | ||
+ | das Kopier- und Verschiebeverhalten von vornherein und | ||
+ | kapseln die Zeiger auf den dynamischen Speicher. | ||
+ | Nutzt man diese schon vorhandenen Klassen zur Datenhaltung in der eigenen Klasse, | ||
+ | braucht man sich um Kopie, Zuweisung und Verschiebesemantik nicht selbst zu kümmern. | ||
+ | So reduziert sich der zu schreibende Quellcode auf wenige, wesentliche Zeilen | ||
+ | ([[begriffe# | ||
+ | <code cpp> | ||
+ | class Matrix | ||
+ | { | ||
+ | public: | ||
+ | explicit Matrix(int n) | ||
+ | : n(n) | ||
+ | , data(n*n, 0.0) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | Matrix() : n(0) {} | ||
+ | | ||
+ | // Elementzugriff, | ||
+ | private: | ||
+ | int n; | ||
+ | std:: | ||
+ | }; | ||
+ | </ | ||
+ | Besitzverwaltende Zeiger auf Einzelobjekte lassen | ||
+ | in referenzzählenden [[kennen: | ||
+ | und nur verschiebbaren [[kennen: | ||
kennen/spezielle_methoden.txt · Zuletzt geändert: 2020-07-27 09:58 von 127.0.0.1