namespace cpp {}

C++ lernen, kennen, anwenden

Benutzer-Werkzeuge

Webseiten-Werkzeuge


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 {};
 +</code>
 +besitzt implizit generierte Methoden zum 
 +Erzeugen, Kopieren, Zuweisen, Verschieben und Vernichten 
 +von [[klassen#Objekte|Objekten]]:
 +<code cpp>
 +struct Empty
 +{
 +  Empty() {}                                             // Standardkonstruktor
 +  Empty(Empty const& rhs) {}                             // Kopierkonstruktor
 +  Empty(Empty&& rhs)      {}                             // Verschiebekonstruktor
 +  Empty& operator=(Empty const& rhs) { return *this; }   // Kopierzuweisung
 +  Empty& operator=(Empty&& rhs)      { return *this; }   // Verschiebezuweisung
 +  ~Empty() {}                                            // Destruktor
 +};
 +</code>
 +In vielen Klassen ist dieses Standardverhalten ausreichend:
 +<code>
 +struct Punkt { int x, y; };
 +
 +void f()
 +{
 +  Punkt a;          // Standardkonstruktor  
 +  Punkt b { 1, 2 }; // POD-Konstruktor
 +  Punkt c (a);      // Kopierkonstruktor
 +  a = b;            // Zuweisung 
 +  std::swap(a, b);  // Tausch nutzt Verschiebekonstruktor und -zuweisung
 +}                   // Destruktor 
 +</code>
 +Die Elemente der Klasse werden erzeugt, kopiert, verschoben und vernichtet, 
 +indem deren spezielle Methoden aufgerufen werden.
 +Einige Regeln sind leicht einzusehen.
 +  * Das "Verschieben" von Grunddatentypen (z.B. ''int'') erfolgt durch Kopieren des Wertes.
 +  * Ein Objekt lässt sich nur dann kopieren bzw. verschieben, wenn die geforderte Aktion für alle Elemente des Objekts ausführbar ist.
 +  * 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, um [[#Besitzverhalten]] und Verwaltung 
 +und [[#Destruktor|Freigabe]] von Ressourcen beim [[#Kopierkonstruktor|Kopieren]] und 
 +[[#Verschiebekonstruktor|Verschieben]] festzulegen.
 +
 +==== default und delete ====
 +Die speziellen Methoden können 
 +ab C++11 durch Markieren als ''= default'' 
 +anstelle eines Funktionsrumpfes mit dem Standardverhalten realisiert
 +oder durch die Angabe ''= delete'' ausdrücklich verboten werden. 
 +(siehe [[.:beispiel:default]]). 
 +Vor C++11 konnten Kopie bzw. Zuweisung unmöglich gemacht werden,
 +indem diese Methoden als ''private'' gekennzeichnet, aber nicht implementiert wurden.
 +
 +
 +==== 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   | ''= default''  |nicht deklariert| ''= default''    |nicht deklariert| ''= default''    |nicht deklariert| ''= default''  |
 +^Destruktor            | ''= default''  | ''= default''  |nutzerdefiniert   | ''= default''(!) | ''= default''(!) | ''= default''  | ''= default''  |
 +^Kopierkonstruktor     | ''= default''  | ''= default''  | ''= default''(!) |nutzerdefiniert   | ''= default''(!) | ''= delete''   | ''= delete''   |
 +^Zuweisungsoperator    | ''= default''  | ''= default''  | ''= default''(!) | ''= default''(!) |nutzerdefiniert   | ''= delete''   | ''= delete''   |
 +^Verschiebekonstruktor | ''= default''  | ''= default''  | nicht deklariert | nicht deklariert | nicht deklariert |nutzerdefiniert |nicht deklariert|
 +^Verschiebezuweisung   | ''= default''  | ''= default''  | nicht deklariert | nicht deklariert | nicht deklariert |nicht deklariert|nutzerdefiniert |
 +
 +(!) ... sollte vom Nutzer verboten oder umdefiniert werden 
 +
 +Besitzt eine Klasse einen nichttrivialen [[#Destruktor]] (Ressourcenfreigabe), 
 +sind zumeist auch [[#Kopierkonstruktor]] und [[#Zuweisungsoperator]] 
 +in geeigneter Weise zu implementieren ([[begriffe#Rule of Three]]). 
 +Die in C++11 eingeführte [[#Verschiebekonstruktor]] und [[#Verschiebezuweisung]] 
 +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#Rule of Five]]).
 +
 +
 +===== 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 ([[.:begriffe#RAII-Prinzip]]). 
 +Der Besitzer ist für die Freigabe [[:lernen:eigentum|verantwortlich]].
 +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::fill(data, data + n*n, 0.0);
 +  } 
 +
 +  Matrix();
 +  ~Matrix();
 +  
 +  Matrix(Matrix const& orig);
 +  Matrix& operator=(Matrix const& rhs);
 +  
 +  Matrix(Matrix&& source);  
 +  Matrix& operator=(Matrix&& rhs);
 +  
 +  // Elementzugriff, operator+=, ...  
 +private:
 +  int n;
 +  double* data;
 +};
 +</code>
 +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::~Matrix() 
 +  {
 +    delete[] data; 
 +  }
 +</code>
 +
 +==== Standardkonstruktor ====
 +Eine leere Matrix hat keinen Speicher. 
 +Dennoch muss der Zeiger sinnnvoll gesetzt werden.
 +<code cpp>
 +  Matrix::Matrix() 
 +  : n(0)
 +  , data(nullptr) 
 +  {
 +  }
 +</code>
 +
 +==== Kopierkonstruktor ====
 +Ein Kopierkonstruktor erzeugt eine Kopie von einem Objekt gleichen Typs.
 +Im Standardverhalten werden die [[#klassen#Attribute]] elementweise kopiert:
 +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 [[#Destruktor]] 
 +zum Freigeben von Ressourcen ist ein verlässlicher Hinweis darauf, 
 +dass Kopierkonstruktor und [[#Zuweisungsoperator]] definiert 
 +oder verboten werden sollten.
 +
 +Für die Matrix wird eine tiefe Kopie erzeugt:{{ :kennen:kopie.png?300|}}
 +<code cpp>
 +  Matrix::Matrix(Matrix const& orig) 
 +  : n(orig.n)
 +  , data(new double[orig.n*orig.n])
 +  {
 +    std::copy(orig.data, orig.data + n*n, data);
 +  }
 +</code>
 +Andere Herangehensweisen wären gemeinsamer Besitz (shared resource)
 +oder lazy copying (copy on write).
 +
 +==== Zuweisungsoperator ====
 +{{ :kennen:zuweisung.png?300|}}
 +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::operator= (Matrix const& rhs)
 +  {
 +    if (this != &rhs)
 +    {
 +      Matrix tmp(rhs);
 +      std::swap(tmp.data, data);
 +      std::swap(tmp.n, n);   
 +    }
 +    return *this;
 +  }
 +</code>
 +
 +==== Verschiebekonstruktor ====
 +Beim Verschieben geht der Besitz von Ressourcen auf das neue Objekt über. 
 +Der Verschiebekonstruktor (engl. move constructor) 
 +übernimmt dazu die Elemente eines als [[.:begriffe#Rvalue-Referenz]] 
 +übernommenen Objekts.
 +Als nun leere Hülle bekommt die Quelle im Austausch 
 +einen ''nullptr'' zum Vernichten: 
 +
 +{{ :kennen:verschieben.png?300|}}
 +<code cpp>
 +  Matrix::Matrix(Matrix&& source)
 +  : n(0)
 +  , data(nullptr)
 +  {
 +    std::swap(source.data, data);
 +    std::swap(source.n, n);   
 +  }  
 +</code>
 +
 +==== Verschiebezuweisung ====
 +Die Verschiebezuweisung (engl. move assignment)
 +ist dann in ähnlicher Weise zu definieren:
 +<code cpp>
 +  Matrix& Matrix::operator= (Matrix&& source)
 +  {
 +    std::swap(source.data, data);
 +    std::swap(source.n, n);   
 +    return *this;
 +  }
 +</code>
 +Kopier- und Verschiebezuweisung können in einer Methode zusammengefasst werden 
 +([[http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap|copy-and-swap-Idiom]], auch "Rule of Four and a half" genannt):
 +<code cpp>
 +  Matrix& Matrix::operator= (Matrix rhs)
 +  {
 +    std::swap(rhs.data, data);
 +    std::swap(rhs.n, n);   
 +    return *this;
 +  }
 +</code>
 +Hier ist ''rhs'' schon eine Kopie, falls notwendig. Zwischenergebnisse werden per Verschiebekonstruktor übernommen oder die Kopie ganz vermieden ([[wp>copy_elision|copy elision]]). 
 +
 +==== Verschiebesemantik bringt Zeitgewinn ====
 +Werden nun noch die fehlenden Rechenoperationen 
 +definiert (Verschiebekonstruktor für ''a'', falls Zwischenergebnis,
 +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; 
 +  // ...   
 +}
 +</code>
 +erfordern die Anweisungen zur Berechnung der Matrix ''c''
 +durch die Verschiebesemantik nur noch zwei statt sechs teure Kopien 
 +für die ersten Zwischenergebnisse. 
 +
 +===== Von Containern bereitgestellt ==== 
 +Das Arbeit mit "nackten", besitzverwaltenden Zeigern 
 +bringt viel Verantwortung mit sich und kann im Ausnahmefall(!) schiefgehen.
 +[[stl#Container]] wie ''std::vector<T>'' implementieren 
 +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#Rule of Zero]]):
 +<code cpp>
 +class Matrix
 +{
 +public:
 +  explicit Matrix(int n) 
 +  : n(n)
 +  , data(n*n, 0.0) 
 +  {
 +  }
 +
 +  Matrix() : n(0) {}
 +  
 +  // Elementzugriff, operator+=, ...  
 +private:
 +  int n;
 +  std::vector<double> data;
 +};
 +</code>
 +Besitzverwaltende Zeiger auf Einzelobjekte lassen 
 +in referenzzählenden [[kennen:lib:shared_ptr|std::shared_ptr<T>]] 
 +und nur verschiebbaren [[kennen:lib:unique_ptr|std::unique_ptr<T>]] verbergen.
  
kennen/spezielle_methoden.txt · Zuletzt geändert: 2020-07-27 09:58 von 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki