C++0x: RValue reference und move constructor

Schnelle objektorientierte, kompilierende Programmiersprache.
Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

C++0x: RValue reference und move constructor

Beitrag von fat-lobyte » Do Jun 09, 2011 11:19 pm

Hallo. Ich wollt nur mal kurz erwähnen, was denn das neue C++ tolles hervorgebracht hat.

Das Problem:

Betrachtet folgende Situation:

Ich möchte ein Objekt aus einem anderen Objekt initialisieren, z.B. so:

Code: Alles auswählen

A a1(1);
A a2 = a1;
Möchte ich das machen, benötige ich einen sogenannten Kopierkunstruktor, also in struct A so etwas wie:

Code: Alles auswählen

A(const A& other) : x(other.x), y(other.y) {}
Soweit sogut. Dieser Fall, dass ich tatsächlich unterschiedliche Objekte habe, die danach auch beide benutzt werden tritt in meiner Erfahrung eher selten ein. Oft initialisiere ich ein Objekt über ein anderes, temporäres Objekt

Code: Alles auswählen

a1 = A(2);
Ich könnte auch eine "Fabrikfunktion" bereitstellen, die das gleiche tut:

Code: Alles auswählen

A makeA(int x)
{ return A(2); }

a1 = makeA(2);
Der Haken an der Sache: A wird hier jedes mal erstellt, kopiert und anschließend wieder gelöscht! Bei einer trivialen Klasse mit ein paar wenigen Membern macht das keinen Unterschied, aber wie siehts mit Klassen aus die größere Daten beinhalten?
Betrachtet folgenden Code:

Code: Alles auswählen

struct A
{
	static const std::size_t ALOT = 1000;

	// default constructor
	A() 
	{ 
		std::cout<<(this)<<": Hi!\n";
		hugedata = new int[ALOT];
	}

	// copy constructor
	A(const A& other)
	{ 
		std::cout<<this<<" was copy constructed.\n";

		hugedata = new int[ALOT];
		std::copy(other.hugedata, other.hugedata+ALOT, hugedata);
	}

	// assignment operator
	A& operator = (const A& other)
	{ 
		std::cout<<this<<" was assigned.\n";

		if (this == &other) return *this;

		hugedata = new int[ALOT];
		std::copy(other.hugedata, other.hugedata+ALOT, hugedata);
		
		return *this;
	}

	void whatup()
	{
		std::cout<<"Whatup? "<<*hugedata<<'\n';
	}

	~A() 
	{  
		if (hugedata) 
		{
			delete[] hugedata;
			hugedata = NULL;
		}
		std::cout<<this<<": Bye!\n"; 
	}
private:
	int *hugedata;
};

A makeA()
{
	return A();
}

int main()
{
	std::cout<<"Default-constructing: \n";
	A a1;
	/* A& a_lr = A(); // <-- Leider nein!!
	a_lr.whatup(); */ 

	std::cout<<"Copy-Constructing:\n";
	A a2 = a1;

	std::cout<<"Assignment from temporary object:\n";
	a1 = A();

	std::cout<<"Assignment from factory:\n";
	a2 = makeA();

	std::cout<<"Free to go.\n";
}
Die Ausgabe des Programms ist folgende:

Code: Alles auswählen

Default-constructing:
0044F75C: Hi!
Copy-Constructing:
0044F750 was copy constructed.
Assignment from temporary object:
0044F678: Hi!
0044F75C was assigned.
0044F678: Bye!
Assignment from factory:
0044F684: Hi!
0044F750 was assigned.
0044F684: Bye!
Free to go.
0044F750: Bye!
0044F75C: Bye!
Für die Zuweisungen, initialisierungen aus temporären Objekten und initialisierung aus "Fabriksfunktionen" wurde jedes mal ein Objekt erstellt, das andere Objekt damit initialisiert und anschließend das temporäre Objekt wieder gelöscht.
Wie ihr in diesem Beispiel seht, musste dazu jedes mal ein neues Array allokiert, die Daten kopiert und das Array wieder gelöscht werden!

Das braucht Zeit und Ressourcen, ist sehr unschön und sollte vermieden werden.

Wollte man das bisher vermeiden, musste man bisher mit Zeigern arbeiten. Das ist umständlich, denn auch wenn man sich mit "smarten" Zeigern das Leben erleichtert, das Objekt allokieren muss man sowieso. Außerdem arbeite ich persönlich viel lieber mit Referenzen, da ich dabei die Garantie habe dass das Objekt existiert, und nicht ein "Wenn dieses new funktioniert hat und kein anderer das Objekt gelöscht hat".

Die Lösung

Das neue C++ schafft abhilfe mit der sogenannten RValue-Referenz.

Eine RValue-Referenz tut erstmal das gleiche wie eine normale (LValue-Referenz), man kann Objekte daran binden und sie dann wie das Objekt selbst verwenden:

Code: Alles auswählen

A a;
A& lr = a; // Binde a an gewöhnliche LValue-Referenz
A&& rr = a; // Binde a an RValue-Referenz

lr.machwas();
rr.machwasanderes();
Der Unterschied wird erst bemerkbar, wenn man Versucht ein temporäres Objekt daran zu binden:

Code: Alles auswählen

A& a_lr = A(); // Fehler! "cannot convert from 'A' to 'A &'  A non-const reference may only be bound to an lvalue"
A&& a_rr = A(); // Geht klar.
a_rr bleibt nun nämlich im Gesamten Gültigkeitsbereich am Leben. Wofür kann ich das brauchen fragt ihr euch? Nun, zugegeben in diesem Beispiel noch nicht. Allerdings können jetzt auch Funktionen RValue-Referenzen als Parameter überreicht bekommen und den (möglicherweise temporären) Wert verändern.
Besonders interessant wird das jetzt für Konstruktoren und Zuweisungsoperatoren! Wir können jetzt nämlich einen ganzen RValue, mitsamt seiner ganzen Daten und Ressourcen, den wir von einem temporären Objekt erhalten haben an uns reißen!

Dazu definieren wir den "move-constructor", und den "move-assignment"-Operator:

Code: Alles auswählen

// move constructor
	A(A&& other)
	{ 
		std::cout<<this<<" was move constructed.\n";

		hugedata = other.hugedata;
		other.hugedata = NULL;
	}

	// move assignment
	A& operator = (A&& other)
	{ 
		std::cout<<this<<" was move-assigned.\n";

		if (this == &other) return *this;

		hugedata = other.hugedata;
		other.hugedata = NULL;
		
		return *this;
	}
Wie ihr seht, sind hier die die teuersten Operationen verschwunden. Kein allokieren, kein Kopieren, ihr "klaut" euch einfach die Daten der RValue Referenz. "Zur Sicherheit" werden die Daten der RValue-Referenz auf ungültige Werte gesetzt (also z.B. NULL).

Jetzt probieren wir diese veränderte Klasse mal aus:

Code: Alles auswählen

std::cout<<"Default-constructing: \n";
A a1;

std::cout<<"Move-Constructing:\n";
A a2(std::move(a1)); // a1 hat nun seine ressourcen aufgegeben!

std::cout<<"Assignment from temporary object:\n";
a1 = A();

std::cout<<"Assignment from factory:\n";
a2 = makeA();

std::cout<<"Free to go.\n";
Die Ausgabe sieht nun folgendermaßen aus:

Code: Alles auswählen

 ===== Testing B: =====
Default-constructing:
0048F8A0: Hi!
Move-Constructing:
0048F894 was move constructed.
Assignment from temporary object:
0048F7BC: Hi!
0048F8A0 was move-assigned.
0048F7BC: Bye!
Assignment from factory:
0048F7C8: Hi!
0048F894 was move-assigned.
0048F7C8: Bye!
Free to go.
0048F894: Bye!
0048F8A0: Bye!
Wie ihr seht, werden die temporären Objekte nun zwar immer noch erstellt und anschließend gelöscht, allerdings werden die enthaltenen Daten und Ressourcen übertragen anstatt einfach kopiert. Das spart Laufzeit.

Vielleicht fragt ihr euch, was denn nun das std::move() darstellen soll. Das tut nämlich folgendes:
Normalerweise wird bei der Überladung die LValue-Referenz bevorzugt. Das ist eine Sicherheitsmaßnahme, damit nicht unabsichtlich das Objekt zerstört wird, wenn man eigentlich kopieren wollte (und um bereits vorhandenen Coden nicht zu zerstören). Deshalb wird hier explizit dargestellt dass man die move-Semantik verwenden möchte, und das funktioniert eben über std::move.

Und wozu brauch ich das jetzt in Echt?
1) Wie gesagt, wenn ihr verhindern wollt das große Daten und Ressourcen kopiert werden müssen
2) Implementierung von "Quellen und Senken": ihr könnt ein einziges Objekt nun tatsächlich weitergeben (anstatt zu kopieren), so können Beispielsweise "Fabrikfunktionen" implementiert werden, die ein Objekt auf die richtige Weise (was auch immer das sein mag) erstellt, es initialisiert und dann weitergibt - und das ohne Overhead!
3) Implementierung von "Perfect Forwarding", also das erstellen von generischen (im Sinne von Templates) Wrappern

Wo kompiliert das Teil jetzt überhaupt?
Visual Studio ab 2010,
GCC ab 4.3 (mit -std=c++0x)
Und vielleicht noch ein paar, die mir aber leider nicht bekannt sind.

Den Code aus diesem Post könnt ihr in einem kleinen Testprojekt herunterladen:
moveconstructor.zip
Es sollte sowohl mit Visual Studio als auch mit GCC kompilieren.

Also Leute, lesen, denken, ausprobieren und einsetzen!!
Gehet hin und implementieret es!
Natürlich würde ich mich sehr über Rückmeldungen freuen und ich werde versuchen Fragen so gut wie möglich zu beantworten. (Erwartet nicht zu viel, das Feature ist auch für mich neu!)
Ganz besonders interessant wären unerwartete Probleme oder Fallen die eventuell auftauchen, oder die ihr euch einfallen lasst.


Hoffe auf eine angeregte Diskussion,
euer fat-c++0x-lobyte ;-)
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: C++0x: RValue reference und move constructor

Beitrag von Xin » Do Jun 09, 2011 11:33 pm

Gibt es schon eine brauchbare Übersicht über die C++0x Funktionen?
Früher oder später will ich mich da mal in Ruhe einarbeiten, gerade auf virtual overload warte ich.
Dein Beispiel werde ich mir auf jeden Fall noch genauer ansehen.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: C++0x: RValue reference und move constructor

Beitrag von fat-lobyte » Fr Jun 10, 2011 8:58 am

Xin hat geschrieben:Gibt es schon eine brauchbare Übersicht über die C++0x Funktionen?
Die brauchbarset übersicht findet man hier:
http://www2.research.att.com/~bs/C++0xFAQ.html
Xin hat geschrieben:Früher oder später will ich mich da mal in Ruhe einarbeiten, gerade auf virtual overload warte ich.
Dein Beispiel werde ich mir auf jeden Fall noch genauer ansehen.
Was is das denn jetzt wieder? Davon hab ich noch nicht gehört...
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: C++0x: RValue reference und move constructor

Beitrag von Xin » Fr Jun 10, 2011 10:34 am

fat-lobyte hat geschrieben:
Xin hat geschrieben:Früher oder später will ich mich da mal in Ruhe einarbeiten, gerade auf virtual overload warte ich.
Dein Beispiel werde ich mir auf jeden Fall noch genauer ansehen.
Was is das denn jetzt wieder? Davon hab ich noch nicht gehört...
Das heisst in C++ auch overwrite, nicht overload.

Man kennzeichnet damit, dass man eine Methode überscbreiben möchte.
Wenn es keine Methode gibt, die man überschreiben kann - z.B. weil man sie umbenannt hat - dann meckert der Compiler. Eins der wichtigsten Features, die mir in C++ fehlen.
Wobei mir das von Dir vorgestellte Feature auch sehr wichtig ist.

Vielleicht sollten wir mal eine Übersicht ins Wiki schreiben, schon alleine, um selbst mit den neuen FeTures vertraut zu werden.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: C++0x: RValue reference und move constructor

Beitrag von Kerli » Sa Jun 11, 2011 10:32 am

Xin hat geschrieben:Vielleicht sollten wir mal eine Übersicht ins Wiki schreiben, schon alleine, um selbst mit den neuen FeTures vertraut zu werden.
Das wäre vermutlich ganz hilfreich :)

Meine Favoriten (zusätzlich zu den bereits genannten sind:
  • Lambdafunctions
  • Threads
  • Endlich ein plattformunabhängiger high-performance timer
  • Smart Pointer
  • Neuen Möglichkeiten der Initialisierung (initializer_list!)
  • "Datentyp" 'auto' (Datentyp einer Variable automatisch bestimmen lassen. Vor allem mit komplizierten Templates nützlich)
  • Type-Traits
  • Range for loops (zb for(int x: {1,2,3,4}) std::cout << x << std::endl;)
  • Template Alias
  • In-class member initializers
  • Explicit auch für Operatoren
  • Verbesserte Enums
  • Nullpointer die nicht mehr automatisch nach int konvertiert werden (Hilfreich bei Überladungen)
  • Und eigentlich eh noch fast alles andere :P
Das sind jetzt eigentlich doch recht viele Sachen geworden, aber es gibt einfach so viele tolle Sachen dabei^^
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: C++0x: RValue reference und move constructor

Beitrag von Xin » So Jun 12, 2011 5:03 pm

Mir scheint ich muss mich bald mal mit den Feinheiten von C++ auseinandersetzen zumal der GNU-Compiler vieles ja schon kann.

Wie weit seid ihr denn schon fit, wenn ihr hier schon lange Listen aufführen könnt? Ich muss mich da endlich auch mal reinlesen bevor ich als C++-Programmierer zum alten Eisen gehöhte ;-)

Hat es jemand von euch schon in aktiver Entwicklung?
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: C++0x: RValue reference und move constructor

Beitrag von fat-lobyte » So Jun 12, 2011 5:24 pm

Xin hat geschrieben:Hat es jemand von euch schon in aktiver Entwicklung?
Die RValue References wollte ich bald implementieren, deswegen habe ich mich eben damit auseinander gesetzt.

Die ganzen Kleinigkeiten (Right Angle Brackets, Auto, Template Aliases, Range-For loops) werde ich wohl auch bald einesetzen. Außerdem wollte ich (allein um Inkompatibel zu werden ;-) ) alle boost::shared_ptr in std::shared_ptr und alle boost::thread in std::thread umwandeln.
Haters gonna hate, potatoes gonna potate.

exbs
Beiträge: 11
Registriert: Mo Sep 29, 2008 12:51 pm

Re: C++0x: RValue reference und move constructor

Beitrag von exbs » Mi Jun 15, 2011 2:07 pm

Wie kann ich auf den neuen Standard umsteigen ? Oder ist es so, dass man sich die Feature heraus pickt, die man nutzen möchte ?

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: C++0x: RValue reference und move constructor

Beitrag von Xin » Mi Jun 15, 2011 5:20 pm

Ich versuche gerade daraus ein Mini-Tutorial bzw. einen cpp:article:-Namensraum zu gestalten, der das dann beschreiben wird. ^^

Soweit ich weiß, muss man beim GCC einen Schalter setzen, um den C++0x Support hinzuzufügen.

Auf der Suche nach dem Switch fand ich erstmal eine Liste der bisher unterstützten Features und der Roadmap der noch (vielen) fehlenden Features.

Dort fand ich auch den Switch den man dem Compiler mitgeben muss, um den g++ mit C++0x zu verwenden:

Code: Alles auswählen

-std=gnu++0x
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Dirty Oerti
Beiträge: 2229
Registriert: Di Jul 08, 2008 5:05 pm
Wohnort: Thurndorf / Würzburg

Re: C++0x: RValue reference und move constructor

Beitrag von Dirty Oerti » Mi Jun 15, 2011 10:20 pm

So eine Section im Wiki fände ich auch sehr gut.
Ich komme langsam auch mit der Uni wohl wieder von Java frei, und will mal wieder eine anständige Sprache benutzen :)
Gleich sich den neuen Standard anzutrainieren ist da natürlich eine super Idee :)
Bei Fragen einfach an daniel[ät]proggen[Punkt]org
Ich helfe gerne! :)
----------
Wenn du ein Licht am Ende des Tunnels siehst, freu dich nicht zu früh! Es könnte ein Zug sein, der auf dich zukommt!
----
It said: "Install Win95 or better ..." So I installed Linux.

Antworten