Einfacheres C++ mit C++11/14
Im M&F Trainee-Programm hatten wir einen spannenden Workshop über Programmierung mit C++11/14. Unser Referent war Peter Sommerlad. Er ist "Director of the IFS Institute for Software" an der Hochschule für Technik in Rapperswil sowie im ISO C++ Komitee und ausserdem Co-Autor von mehreren Büchern.
In meiner Zusammenfassung werden Neuerungen von C++11/14 vorgestellt, sowie einige Hilfsmittel und sonstige Coding-Guidelines.
CUTE
Ein Hilfsmittel (frei erhältlich) zum Testen von C++ Code und gut geeignet für Test-Driven Design bzw. Refactoring.
auto
Ähnlich wie 'var' für die .NET Welt wird auto genutzt, um den Deklarationstypen automatisch zu bestimmen. Beispielsweise statt int a = 1; kann man auto a = 1; schreiben, da der Typ schon automatisch durch die Initialisierung bestimmt wurde. Auch bei Funktionen (inkl. Templates) kann auto eingesetzt werden.
! Hierbei muss man zwischen C++11 und C++14 unterscheiden. Beide unterstützen auto, jedoch kann C++14 noch mehr damit anfangen, z.B. man kann so den Rückgabetypen einer Methode genauer definieren.
Falls man nicht nur den Typen abkürzen möchte, sondern auch Referenzen (z.B. int&) so sollte man decltype(auto) verwenden.
Universal Initializer
Um Variablen und Listen/Arrays einfach und schnell zu initialisieren, gibt es nun den universal initializer {} zum initialisieren von solchen. Beispiel: std::vector v{3,1,4,1,5,9,2,6};
() rounD Definition, Declaration -- {} Curly Construction, Creation
Algorithmen benutzen, statt selber schreiben
Es gibt viele Algorithmen in der Standardbibliothek, welche sehr optimiert sind und z.B. Out-of-Bounds-Fehler vermeiden. Anstatt for/while loops sollte man wenn möglich diese benutzen. Eine kleine Auswahl von Beispielen:
-
std::distance()
-
std::for_each()
-
std::accumulate()
Beispiele für Vektor v:
Einfache for-Schleife: for(auto const i:v){...}
Oder Iteratoren v.begin(), v.end() verwenden: for (auto it=begin(v); it!=end(v); ++it){...}
Lambda
Ab C++11 werden Lambda-expressions unterstützt, welche dazu dienen, Funktionen wie Variablen zu behandeln und diese einfach zu deklarieren. Beispiel: auto printHello=[] { cout "hello world" endl;};
Special case: Falls die Variable innerhalb einer Lambda-expression veränderbar sein sollte, so muss die Lambda-expr. als mutable gekennzeichnet werden.
Functors
Ein Funktor ist eine Klasse, welche den ()-Operator überschreibt:
struct
donothingfunctor{
void
operator()()
const
{}
};
Predicates
Predicates sind spezielle Lambdas oder Funktionspointer, welche einen Boolean oder einen Boolean-konvertierbaren Typ (Funktor) zurückgeben.
Sie werden häufig bei Container-Algorithmen verwendet um die zu verwendenden Elemente zu definieren.
Predicate
auto odd=[](int i){ return i%2; };
(Smart) Pointers
Es sollten keine normalen Pointers (z.B. new T{}) mehr verwendet werden, welche man dann selber wieder aufräumen muss. Stattdessen gibt es verschiedene Alternativen:
-
nullptr null pointer
-
shared_ptr Pointer mit reference counting => bei 0 references wird er zerstört.
-
unique_ptr non-shared pointer.
Simpler Classes
Sommerlad's rule of zero
Write your classes in a way that you do not need to declare/define neither a destructor, nor a copy/move constructor or copy/move assignment operator
New Value Terminology (lvalue, rvalue)
5 Errortypen von Funktionen
Es gibt fünf Möglichkeiten Fehler in einer Funktion abzufangen:
- ignore the error and provide potentially undefined behavior
- return a standard result to cover the error
- return an error code or error value
- provide an error status as a side effect
- throw an exception
Implementierung von Arithmetic Types
Wenn möglich sollte man dies vermeiden. Wenn nicht vermeidbar, so sollte man wenn möglich die Boost-Bibliothek benutzen. Sie benötigt dafür folgende Operationen:
- operator== z.B. wird mit boost::equality_comparable {...} implementieren
- operator()
- operator+= z.B. mit boost::addable {...} implementieren
- operator*=
Templates
Templates dienen dazu, gewisse Funktionen/Abläufe für verschieden Typen anzubieten (z.B. Int und Double) ohne Code zu duplizieren. Dazu gibt es verschiedene Arten von Templates:
Function Template
Eine Funktion kann mit verschiedenen Typen gebraucht werden. Hier ein Beispiel mit der Implementation einer Minimums-Funktion:
template
typename
T>
T
const
& min(T
const
& a, T
const
& b){
return
(a b? a : b ;
}
Variadic Template Function
Diese Template-Art wird gebraucht, falls die Anzahl Argumente für eine Template-Funktion variabel sein kann. Beispiel:
template
typename
...ARGS>
void
variadic(ARGS...argsargs){
println(std::cout,args...);
}
Klassen Template
Zusätzlich zu einzelnen Funktionen können ganze Klassen als Template definiert werden. Die Syntax hierzu ist sehr ähnlich zu den obigen Beispielen. Im Gegensatz zu einem Funktionstemplate müssen aber die Typen bei der Deklaration immer genau angeben werden (z.B. std::vector)
Beispiel einer Klassen-Template-Definition: template class Sack;
Patterns in C++
Viele Patterns sind schon in der Standard Library enthalten (z.B. Iteratoren). Ansonsten sollten Patterns immer dort verwendet werden, wo sie Sinn ergeben.
Das Singleton-Pattern sollte nicht mehr verwendet werden.
Fun Facts aka nice to know:
- std::vector ist der wichtigste Container
- Boost Libraries stellen viele nützliche Features zur Verfügung (siehe http://boost.org)
Mehr zum Thema erfahren Sie in der Präsentation von Peter Sommerlad.
Kommentare