Advanced Java Services | move-semantics |
Sowohl die Kopiervorgänge im Kopierkonstruktor als auch die in der Operatorfunktion operator=() brauchen Speicher und kosten Zeit, da die Kopiervorgänge über temporäre Speicher ablaufen. Wird so ein temporärer Speicher nur einmal benützt und nie mehr wieder, so gelingt es mitHilfe von rValue-Referenzen solche Speicher in einen persistenten Speicher umzuwandeln. Dazu greift man in das Speichermanagement derart ein, daß man einen regulären Pointer auf diesen Bereich setzt und den temporären Pointer, der auf diesen Speicher zeigt auf NULL setzt. Auf diese Weise verhindert man das Löschen des temporären Speichers. Eine rValue-Referenz in der Parameterliste einer Funktion zeigt an, daß man mit dieser Technik arbeiten kann. Dazu werden der Kopierkonstruktor und der Zuweisungsoperator, die in jeder Klasse vom Compiler erzeugt werden um einen move-Konstruktor und einen move-Zuweisungsoperator ergänzt. Damit besitzt jede Klasse 5 vom Compiler erzeugte Konmstruktoren bzw. Operatorfunktionen, die vom Compiler automatisch erzeugt werden, die man aber auch selbst überschreiben kann. Zudem gibt es in C++11 globale swap- und move-Funktionen, die mit diesem neuen Feature arbeiten.
Im folgenden werden alle fünf Standardkonstruktoren bzw. Zuweisungsoperatoren mit ihren Signaturen aufgelistet.
Funktion | Signatur |
Parameterloser Konstruktor | Xxx( ) |
Kopierkonstruktor | Xxx( const Xxx &) |
move-Konstruktor | Xxx( Xxx && ) |
Zuweisungsoperator | Xxx & operator=( const Xxx & ) |
move-Zuweisungsoperator | Xxx & operator=( Xxx && ) |
Im folgenden wird eine einfache Stringklasse entworfen. Es wird ein move-Konstruktor und ein move-Zuweisungsoperator geschrieben und gezeigt, daß die beiden Methoden verwendet werden.
Die Headerdatei
#ifndef STRING_H_ #define STRING_H_ #include <iostream> using namespace std; /* */ class String { char* data; public: // default String(); // Constructor String(const char* p); //// Copy-Constructor klassisch String(const String& that); //// move constructor String(String&& that); // String&& is an rvalue reference to a string //// operator=() // classical implementation String& operator=(const String & that); //// move-assignment String& operator=(String&& other); // Destruktor virtual ~String(); // virtual nur hier! char* c_str() const; }; #endif // STRING_H_
Die Implementierung
#include "String.h" /* Defaultkonstruktor erzeugt Leerstring */ String::String() { //cout << "constructor String()" << endl; this->data = new char[1]; *this->data = 0; // this->data = ""; dieser Speicher kann nun nicht mehr freigegeben werden! } /* Initialisiert einen String mit einem c-String */ String::String(const char* cstr) { //cout << "constructor String(const char*)" << endl; if (cstr != NULL) { size_t size = strlen(cstr) + 1; this->data = new char[size]; memcpy(this->data, cstr, size); } //else ... } /* copy-Constructor */ String::String(const String& other) { cout << "copy-constructor: String(const String&)" << endl; size_t size = strlen(other.data) + 1; this->data = new char[size]; if (this->data !=NULL) memcpy(this->data, other.data, size); // else... } /* move-Constructor oder auch : swap(*this, other); */ String::String(String&& other) //: data(other.data) { cout << "move-constructor: String(String&&)" << endl; this->data = other.data; other.data = NULL; } /* assignment operator=(const X &); // http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom */ String& String::operator=(const String & other) { cout << "assignment: operator=(const String&)" << endl; if( this != &other) { // other ist zumindest ein leerstring, other.data ist also nie NULL, es gibt daher immer was zu kopieren. size_t size = strlen(other.data) + 1; // strlen zählt die 0 nicht mit ! delete[] this->data; this->data = new char[size]; memcpy(this->data, other.data, size); } return *this; } /* move assignment operator=(X&&) // Move semantics: exchange content between this and rhs and return *this . */ String& String::operator=(String&& other) { cout << "move assignment: operator=(String&&)" << endl; // other ist zumindest ein leerstring, other.data ist also nie NULL, es gibt daher immer was zu kopieren. this->data = std::move(other.data); other.data = NULL; //std::swap(this->data, other.data); //this->data = other.data; other.data = NULL; return *this; } // Destruktor String::~String() { //cout << "destructor" << endl; delete[] data; } char* String::c_str() const { return this->data; }
Die Verwendung
#include "String.h" String testfunc1(String s); String testfunc2(String* s); String* testfunc3(String* s); int main() { cout << "main" << endl; // String s("Hi") ; cout << "---------- calling testfunc1 -----------" << endl; s = testfunc1(s); cout << "\n---------- calling testfunc2 -----------" << endl; s = testfunc2(&s); cout << "\n---------- calling testfunc3 -----------" << endl; String* t = testfunc3(&s); cout << "end main" << endl; // return 0; }
Screenshot