Mini-Kurs "Move-Semantik in C++"
C++ ist schon immer für hohe Performance und schnelle Programme bekannt gewesen, aber mit der Einführung von C++11 ist die Sprache noch einmal deutlich effizienter geworden - nicht zuletzt durch die Einführung der Move-Semantik.
Mit Hilfe der Move-Semantik können wir an vielen Stellen im Code die Erstellung von temporären Objekten verhindern sowie die Übergabe von großen Datentypen von einem Geltungsbereich in den nächsten optimieren.
Das Ziel dieses Mini-Kurses ist es, die wesentlichen Ideen der Move-Semantik praxisnah und verständlich mit vielen Beispiele und Experimenten zu vermitteln.
Der Kurs richtet sich an Fortgeschrittene mit einem soliden Grundlagenwissen in C++. Durch den ausführlichen Grundlagenteil wird der Einstieg in die Welt der Move-Semantik jedoch stark erleichert.
Begleitskript zum Kurs
Alle Videos sowie die Code-Beispiele sind frei verfügbar.
Um dich beim Lernen zu unterstützen und um dir ein Nachschlagewerk für die wichtigsten Konzepte der Move-Semantik zu bieten, kannst du dir zusätzlich ein optionales
Begleitskript kaufen.
Auf 58 Seiten findest du hierin detaillierte Erklärungen, zahlreiche Abbildungen und Code-Beispiele, die dir das Lernen und Verstehen deutlich erleichtern werden.
Kursinhalt
-
Grundlagen
-
Teil 1.1 - Stack und Heap
-
Teil 1.2 - Parameterübergabe
-
Teil 1.3 - Copy-Semantik
-
Teil 1.4 - Lvalues und Rvalues
-
-
Move-Semantik
-
Teil 2.1 - Rvalue-Referenzen
-
Teil 2.2 - Die „Rule of Five“
-
-
Praxisbeispiel "Staffellauf"
-
Teil 3.1 - Performance-Vergleich "Copy" vs. "Move"
-
Move-Semantik • Grundlagen
Teil 1.1 : Stack und Heap
Damit fortgeschrittenere Konzepte wie Rvalue-Referenzen oder der Move-Konstruktor richtig verstanden werden können, werden wir uns im ersten Teil des Kurses mit einigen wichtigen Grundlagen befassen.
In diesem Video geht es um den Unterschied zwischen Stack- und Heap-Speicher. Im ersten Teil schauen wir uns gemeinsam Theorie und Hintergründe zu Stack und Heap an und im zweiten Teil testen wir das ganze dann mit Beispielen und Experimenten in der Praxis.
Code-Beispiele aus dem Skript
Listing 1-1-L1
Ziel :
Wachstumsrichtung des Stack-Speichers ermitteln
Ansatz :
Ermittlung der Differenz zwischen den Stack-Adressen eines void-Zeigers und einer double-Variablen. Aus dem Vorzeichen der Differenz lässt sich ableiten, ob der Stack vom oberen Ende des Adressraums nach unten wächst (negatives Vorzeichen) oder in umgekehrter Richtung.
Referenz :
Skript "Move-Semantik in C++", Seite 8
Zurück zum Inhaltsverzeichnis
Weiter zum nächsten Teil
Teil 1.2 : Parameterübergabe
In diesem Video geht es um verschiedene Möglichkeiten, Parameter an Funktionen zu übergeben. Hierbei werden wir uns vor allem mit Call-By-Value und Call-By-Reference beschäftigen. Je nachdem, welche Variante umgesetzt ist, werden Kopien der Argumente beim Aufruf angelegt oder lediglich eine Referenz auf das Original erstellt.
Wenn wir uns schließlich in Teil 2 dieses Buches die eigentliche Move-Semantik ansehen, wird das Einsparen von Kopier-Operationen eine ganz zentrale Rolle spielen. Aus diesem Grund sehen wir uns in diesem Grundlagen-Teil die beiden wesentlichen Optionen zur Parameterübergabe im Detail an. Wie im letzten Kapitel werden wir uns zuerst mit der Theorie beschäftigen, um danach die wichtigsten Konzepte direkt in die Praxis umzusetzen.
Code-Beispiele aus dem Skript
Listing 1-2-L5
Ziel :
Ermittlung der Speicherbelegung von Funktionen für CallByValue
Ansatz :
Definieren einer Funktion callByValue(), die ein Argument vom Typ double aufnehmen kann. Der Rückgabetyp „void-Pointer“ dient wie in einem vorherigen Beispiel dazu, die Position des Stack-Zeigers in der Funktion zurückzugeben. Dann Berechnung der Differenz zwischen der Position vor dem Funktionsaufruf und nach dem Funktionsaufruf in main().
Referenz :
Skript "Move-Semantik in C++", Seite 17
Zurück zum Inhaltsverzeichnis
Weiter zum nächsten Teil
Teil 1.3 : Copy-Semantik
In diesem Video geht es um Strategien beim Kopieren von Objekten. Wir werden sehen, wie wir das Übertragen von Daten von einem Objekt auf das andere mit Hilfe von Mechanismen wie Shallow-Copy und Deep-Copy gezielt beeinflussen können.
Dabei ist eine Regel sehr wichtig, die sogenannte Rule Of Three. Wir befassen uns dabei intensiv mit dem Destruktor, dem Copy-Konstruktor und dem Copy-Zuweisungsoperator und lernen, wie wir diese Komponenten an unsere Bedürfnisse anpassen können, um Heap-Speicher sicher zu verwalten. Wie im letzten Kapitel sehen wir uns erst die notwendige Theorie an, danach geht es dann wieder in die Praxis.
Code-Beispiele aus dem Skript
Listing 1-3-L9
Ziel : Implementierung und Instanziierung einer Klasse, welche die Rule of Three befolgt
Ansatz :
Implementierung von Destruktor, Copy-Konstruktor und Copy-Zuweisungsoperator sowie deren Aufruf in main().
Referenz :
Skript "Move-Semantik in C++", Seite 25
Zurück zum Inhaltsverzeichnis
Weiter zum nächsten Teil
Teil 1.4 : Lvalues und Rvalues
Im vierten und letzten Grundlagen-Kapitel werden wir uns mit zwei elementaren Wertekategorien in C++ befassen, nämlich Lvalues und Rvalues. Beide zu verstehen ist wichtig, wenn es darum geht, Daten in einem Programm auf effiziente Art und Weise von einer Funktion an die andere zu übergeben.
Wir haben sowohl Rvalues als auch Lvalues bereits verwendet, ohne dies besonders zu erwähnen. Ziel dieses Kapitels ist es, die Eigenschaften beider Wertekategorien zu erläutern und deren elementare Bedeutung für die Move-Semantik klar herauszustellen.
Wenn wir im Hauptteil über Rvalues und Rvalue-Referenzen reden, wird mit den Grundlagen aus diesem Kapitel der wesentliche Mechanismus hinter der Move-Semantik besser zu verstehen sein.
Code-Beispiele aus dem Skript
Listing 1-4-L13
Ziel :
Lvalues und Rvalues im Code erkennen und sicher verwenden.
Ansatz :
Implementierung einer Reihe von Lvales, Rvalues und Zuweisungen zwischen den beiden Wertekategorien
Referenz :
Skript "Move-Semantik in C++", Seite 34
Zurück zum Inhaltsverzeichnis
Weiter zum nächsten Teil
Move-Semantik • Hauptteil
Teil 2.1 : Rvalue-Referenzen
In den vier Videos des Grundlagenteils haben wir uns mit einigen elementaren Konzepten befasst, die für ein solides Verständnis der Move-Semantik wichtig sind.
In diesem ersten Video des Hauptteils werden wir uns ausführlich mit Rvalue-Referenzen befassen - also Alias-Identifiern für temporäre Werte. Ein derartiges Konzept mag auf den ersten Blick relativ trivial klingen, tatsächlich ist es aber der Mechanismus, der die Move-Semantik und die Ideen dahinter überhaupt erst möglich macht.
Sehen wir uns an, warum das so ist.
Code-Beispiele aus dem Skript
Listing 2-1-L16
Ziel :
Sichere Verwendung von Rvalue-Referenzen in unterschiedlichen Situationen
Ansatz :
Zuweisung von Rvalues an Rvalue-Referenzen, Rvalue-Referenzen als Funktionsparameter nutzen sowie Beispiele für fehlerhafte Zuweisungen zwischen Rvalues und Lvalues
Referenz :
Skript "Move-Semantik in C++", Seite 43
Zurück zum Inhaltsverzeichnis
Weiter zum nächsten Teil
Teil 2.2 : Die "Rule of Five"
Nachdem wir im letzten Video Rvalue-Referenzen kennen gelernt haben, ist vielleicht die Frage nach der Sinnhaftigkeit eines derartigen Konstrukts aufgekommen. In diesem letzten Video werden wir uns daher ausführlich mit der Antwort auf diese Frage beschäftigen.
Wir werden uns den eigentlichen Kern der Move-Semantik ansehen, nämlich den Move-Konstruktor und den Move-Zuweisungsoperator. Dabei werden wir die bereits bekannte Rule of Three aus dem Video zur Copy-Semantik um zwei weitere Methoden zur Rule of Five erweitern und eine Klasse schreiben, die in der Lage ist, in sehr effizienter Weise einen größeren Block Heap-Speicher verwaltet.
Code-Beispiele aus dem Skript
Listing 2-2-L18
Ziel :
Implementierung der Rule of Five in einer eigenen Klasse
Ansatz :
Überschreiben von u.a. Move-Konstruktor und Move-Zuweisungsoperator, um ein konsistentes Speichermanagement beim Instanziieren und Kopieren zu erreichen.
Referenz :
Skript "Move-Semantik in C++", Seite 53
Zurück zum Inhaltsverzeichnis
Weiter zum nächsten Teil
Teil 3.1 : Performance-Vergleich "Move" vs. "Copy"
Dies ist das letzte Video im Mini-Kurs. Bisher haben wir uns ausführlich mit den wichtigsten vorbereitenden Grundlagen sowie mit der eigentlichen Move-Semantik beschäftigt. An mehreren Stellen wurde betont, dass uns die Move-Semantik Geschwindigkeitsvorteile gegenüber der Copy-Semantik bringt. Bisher mussten wir das einfach ohne Nachweis glauben.
In diesem Video werden wir daher gemeinsam ein Programm entwickeln, in dem wir die Performance-Unterschiede zwischen Copy und Move untersuchen können. Die Idee ist, einen digitalen Staffellauf zu programmieren, in dem Läufer-Objekte einen virtuellen Staffelstab entweder mit Move- oder mit Copy-Semantik von einer Läufer-Instanz an die nächste übergeben. Wir stoppen dabei die Zeit und schauen uns an, wer gewinnt und wie groß der Geschwindigkeitsunterschied der beiden Methoden am Ende wirklich ist.
Übungen
Listing 3-1
Ziel :
Performance-Vergleich zwischen Copy- und Move-Semantik durchführen
Ansatz :
Implementierung einer Klasse, die sowohl Copy- als auch Move-Semantik unterstützt und Vergleich beider Methoden in einem virtuellen "Staffellauf", in dem Instanzen von einem "Läufer" an den nächsten übergeben werden.
Hinweis: Der Laufzeit-Unterschied zwischen Copy und Move ist beachtlich!
Zurück zum Inhaltsverzeichnis
Wie geht es weiter?
Die Move-Semantik ist eine Schlüssel-Technik, die den Weg zu mehr Performance in der Programmierung mit C++ ebnet. Wenn du die Move-Semantik verstanden hast, dann sind auch Smart Pointer oder Multi-Threading kein großes Problem mehr - denn an wichtigen Stellen wird hier auch "verschoben" statt "kopiert".
Wenn du C++ in deinem (zukünftigen) Beruf professionell nutzen willst, dann möchte ich dir an dieser Stelle das Nanodegree C++ von Udacity empfehlen. Das ist ein professionell angeleiteter Kurs mit Abschluss-Zertifikat, in dem sehr ausführlich und strukturiert modernes C++ vermittelt wird.
Ein Highlight des Kurses sind aus meiner Sicht die vielen Beiträge vom Schöpfer von C++, Bjarne Stroustrup. Zwei der Kursmodule (Speichermanagement und Concurrency) wurden von mir entwickelt und ich denke, dass besonders der hohe Praxisanteil mit vielen Beispielen sehr hilfreich beim Lernen sind.
Udacity-Abschlussprojekt: "Program a Concurrent Traffic Simulation"