Open Source im professionellen Einsatz
Linux-Magazin 08/2015

Modernes C++ in der Praxis – Folge 23

Punktlandung

Klein, aber oho: Platziert ein Entwickler drei Punkte ("…") geschickt an der richtigen Stelle im C++-Code, entpacken die so genannten Variadic Templates ihre Argumente an Ort und Stelle.

1064

Was haben »std::tuple()« , »std::thread()« , »std::make_unique()« und »std::make_shared()« gemeinsam? Als Variadic Templates akzeptieren sie beliebig viele Argumente, ihr Symbol ist die Ellipse. Deren Zweck ist schnell erklärt (siehe Kasten "Ellipse"). Ein Variadic Template enthält Parameter-Packs und gibt die Anzahl der darin enthaltenen Elemente zurück. Dabei tritt das Parameter-Pack wahlweise als Template-Parameter-Pack auf, das keines bis beliebig viele Template-Argumente aufnimmt, oder als Function-Parameter-Pack, das eine analoge Menge an Funktionsargumenten akzeptiert. Anschaulicher erklärt eine Anwendung die neue Begrifflichkeit.

Ellipse

Die drei Punkte »...« , auch Ellipse genannt, haben eine lange Geschichte in C und C++. Sie verwandeln gewöhnliche in spezielle Funktionen, die beliebig viele Argumente annehmen. Diese heißen variadische Funktionen (Variadic Functions) und ihr prominentester Vertreter ist wohl »printf()« .

Der entscheidende Unterschied zwischen variadischen Funktionen und Templates besteht darin, dass die Ersteren nicht typsicher sind. Beispielsweise verhält sich »printf()« unvorhersehbar, wenn sie an eine falsche Typ-Information gerät. Ein typischer Einsatzzweck variadischer Templates besteht aus diesem Grund darin, ein typsicheres »printf()« zu implementieren [1].

Der "sizeof…"-Operator

Der mit C++11 eingeführte »sizeof...« -Operator ermittelt als Variadic Template im Gegensatz zu seinem klassischen Pendant direkt die Anzahl seiner Argumente. Listing 1 zeigt ihn in Aktion.

Listing 1

count.cpp

01 #include <iostream>
02 #include <list>
03
04 template <typename ... Args>
05 int count(Args ... args){
06   return (sizeof...(args));
07 }
08
09 int main(){
10
11   std::cout << std::endl;
12
13   std::list<int> myList{1,2,3,4,5,6,7,8,9};
14
15   std::cout << "count()= " << count() << std::endl;
16   std::cout << "count(one,3.14,myList)= " << count("one", 3.14 , myList ) << std::endl;
17   std::cout << "count(myList)= " << count(myList) << std::endl;
18
19   std::cout << std::endl;
20
21 }

Bei dem »count()« -Funktionstemplate ab Zeile 4 handelt es sich um ein Variadic Template. Dank der Zeilen 15 bis 17 ruft das übersetzte Programm das Funktionstemplate mit keinem, einem oder drei Argumenten auf (Abbildung 1).

Abbildung 1: Der sizeof...-Operator kann in C++11 die Anzahl seiner Argumente ermitteln.

Um das zu erreichen, definiert »typename ... Args« in Zeile 4 zunächst ein Template-Parameter-Pack, in der Folge kann »count()« beliebig viele Template-Argumente annehmen. Ruft das Programm ab Zeile 14 die »count()« -Funktion auf, entpackt es automatisch die Funktionsaufrufe des Function-Parameter-Pack »Args ... args« aus Zeile 5. Abbildung 2 stellt schematisch dar, wie C++ diese auf die entpackten Function-Parameter-Packs abbildet.

Abbildung 2: C++ entpackt automatisch die Function-Parameter-Packs des Funktionstemplate count().

Zwar bestimmt der »sizeof...« -Operator (Zeile 6) problemlos die Elemente des Parameter-Pack »args« , der Entwickler kann aber auch eigene Funktionstemplates einsetzen, um Parameter-Packs zu entpacken.

Unique Pointer

Wer in C++11 einen Shared Pointer (»shared_ptr« , [2]) erzeugen möchte, greift am besten zu der Fabrikfunktion »std::make_shared()« , weil das Funktionstemplate als Variadic Template beliebig viele Argumente annimmt und seine Ressource ohne Umwege direkt erzeugt. Wer in C++11 hingegen zum Funktionstemplate »std::make_unique()« greifen möchte, um einen Unique Pointer [3] zu erzeugen, sucht vergebens, denn diese Fabrikfunktion haben die C++11-Standardisierer schlicht vergessen.

Nun gibt es zwei Optionen: Die Entwickler warten, bis der Compiler den überarbeiteten C++11-Standard C++14 unterstützt und damit auch »std::make_unique()« . Oder sie implementieren »make_unique()« einfach selbst. Aus Gründen der Sportlichkeit kommt natürlich nur Option zwei in Betracht, Listing 2 weist den Weg.

Listing 2

makeUnique.cpp

01 #include <iostream>
02 #include <memory>
03
04 class Point {
05 public:
06   Point(){}
07   Point(int y) :y(y){}
08   Point(int x, int y): x(x), y(y){}
09   friend std::ostream& operator <<(std::ostream& os, Point& p) {
10         return os << "(" << p.x << "," << p.y << ")";
11     }
12 private:
13      int x= 0;
14      int y= 0;
15 };
16
17 template<typename T, typename... Args>
18 std::unique_ptr<T> make_unique(Args&&... args){
19   return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
20 }
21
22 int main(){
23
24   std::cout << std::endl;
25
26   auto myInt= make_unique<int>(2011);
27   std::cout << "*myInt: " << *myInt << std::endl;
28
29   std::cout << std::endl;
30
31   std::cout << "*make_unique<Point>(): " << *make_unique<Point>() << std::endl;
32   std::cout << "*make_unique<Point>(2011): " << *make_unique<Point>(2011) << std::endl;
33   std::cout << "*make_unique<Point> (2011,2014): " << *make_unique<Point> (2011,2014) << std::endl;
34
35   std::cout << std::endl;
36
37 }
38

In den Zeilen 17 bis 20 spiegelt sich die ganze Funktionalität. Das Funktionstemplate besitzt den Template-Parameter »typename T« sowie ein Template-Parameter-Pack: »typename... Args« . Dabei repräsentieren »T« den Typ der zu erzeugenden Ressource und »typename... Args« die Typen seiner Argumente. Dies könnten findige Beobachter auch aus dem »return« -Ausdruck »new T(std::forward<Args>(args)...)« in Zeile 19 schließen.

Das Funktionstemplate »make_unique()« nimmt in »Args&&... arg« (Zeile 18) jedes Element des Function-Parameter-Pack als Rvalue-Referenz an: Dies bewirkt, dass »makeUnique.cpp« sämtliche Argumente identisch an den Konstruktor von »T« übergibt.

Der Konstruktoraufruf »T(std::forward <Args>(args)...)« expandiert aufgrund der Ellipse schließlich das Muster »std::forward<Args>(args)...« . Das bildet den Funktionsaufruf »make_unique<Point>(2011,2014)« (Zeile 33) auf den Konstruktoraufruf »new Point(std::forward<Args>(2011),std::forward<Args>(2014))« ab.

Die Klasse »Point« besitzt drei Konstruktoren mit keinem, einem sowie zwei Argumenten (Zeilen 6 bis 8). Das Function Template »make_unique()« erzeugt die entsprechenden Objekte (ab Zeile 31). Dies betrifft auch die »int« -Variable, die Zeile 26 des Programms mit »2011« initialisiert. Die Zeilen 27 sowie 31 bis 33 dereferenzieren dann die Unique Pointer und geben sie aus. Das ermöglicht die Klasse »Point« , weil Zeile 9 den Ausgabe-Operator überlädt. Abbildung 3 zeigt, was das Programm ausgibt.

Abbildung 3: Das Funktionstemplate make_unique() gibt unterschiedlich viele Werte aus, der zugehörige Code steht in Listing 2.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • C++11

    Die Referenz-Wrapper bilden eine kleine, aber feine neue Bibliothek in C++11. Diese Objekte verhalten sich wie Referenzen, der Artikel erklärt die Details.

  • C++11

    Damit sein Code nicht als Bananensoftware beim Kunden reift, setzt der umsichtige C++-Lieferant auf die Funktion static_assert() und die Type-Traits-Bibliothek. Das dynamische Duo stellt Bedingungen an den Quellcode, die der Compiler zur Übersetzungszeit verifiziert.

  • C++11

    In C++11 lässt sich manches prägnanter formulieren als in klassischem C++. Dieser Artikel zeigt, wie die neue Range-basierte For-Schleife und die automatische Typableitung dabei helfen.

  • C++

    Der neue Standard C++17 kündigt sich für 2017 an. Er wird zwar einige großartige Features wie eine Bibliothek für das Dateisystem mitbringen, doch andere lang ersehnte Funktionen fehlen wohl weiterhin.

  • C++11

    2014 ist ein besonderes Jahr für C++. Drei Jahre nach C++11 erfährt der Sprachstandard mit C++14 den letzten Feinschliff. Neben generischen Lambda-Funktionen und der vereinfachten Ermittlung des Rückgabetyps kann C++14 vor allem mit einem Feature punkten: Reader-Writer-Locks.

comments powered by Disqus

Ausgabe 10/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.