Einheitlich initialisieren

In C++03 können einfache Datenstrukturen (vergleichbar zu C-structs) mit Initialisiererlisten initialisiert werden. Dummerweise sperrt man sich damit von den eigentlich interessanten Aspekten von C++ aus: Vererbung, Laufzeitpolymorphie, Konstruktoren… alles verboten. C++11 behebt diesen Mangel. Mit Uniform Initialization lässt sich das Prinzip auf beliebige Datentypen anwenden.

Video

Code

Zuerst die C++03-Variante:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
 
struct Point
{
  int x;
  int y;
 
  void draw()
  {
    std::cerr << "Point(" << x << ", " << y << ")\n";
  }
};
 
struct Triangle
{
  Point p1, p2, p3;
   
  void draw()
  {
    std::cerr << "Triangle(\n";
    p1.draw();
    p2.draw();
    p3.draw();
    std::cerr << ")\n";  
  }
};
 
int main()
{
  Point p = { 10, 5 };
  Triangle t = {
    { 4, 5 },
    { 7, 2 },
    { 9, 2 }
  };
   
  p.draw();
  t.draw();
}

C++11 verallgemeinert dieses Prinzip stark:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
 
class Shape
{
  public:
    virtual void draw() = 0;
};
 
class Point : public Shape
{
  int x;
  int y;
   
public:
   
  Point(int x_, int y_)
    : x{x_}, y{y_}
  {
  }
   
  void draw()
  {
    std::cerr << "Point(" << x << ", " << y << ")\n";
  }
};
 
class Triangle : public Shape
{
  Point p1, p2, p3;
   
public:
   
  Triangle(Point p1_, Point p2_, Point p3_)
    : p1{p1_}, p2{p2_}, p3{p3_}
  {
  }
   
  void draw()
  {
    std::cerr << "Triangle(\n";
    p1.draw();
    p2.draw();
    p3.draw();
    std::cerr << ")\n";
  }
};
 
int main()
{
  Point p { 10, 5 };
  Triangle t {
    { 5, 7 },
    { 2, 3 },
    { 1, 9 }
  };
   
  p.draw();
  t.draw();
};

Erklärung

Das C++03-Beispiel zeigt die Mächtigkeit dieser Initialisierungsart in der main()-Funktion. Kurz und knackig kann selbst die verschachtelte Struktur Triangle mit ihren enthaltenen Points initialisiert werden. Die Initialisierung geschieht in der Reihenfolge, in der die Membervariablen in der Klasse stehen: für Point bekommt x den ersten, y den zweiten Wert in der Initialisierliste. Bei Triangle wird p1 aus dem Point initialisiert, der aus der ersten Teilliste erzeugt wird und so weiter.

Der Preis für die Bequemlichkeit ist hoch: Point und Triangle dürfen nicht von einer anderen Klasse abgeleitet sein, keine privaten Variablen, keine Konstruktoren und keine virtuellen Funktionen enthalten (es gelten noch ein paar Detailbedingungen, die aber praktisch wenig relevant sind). Damit sind sie letzten Endes fast normale C-structs, denen fast alle interessanten C++-Fähigkeiten fehlen (das einzige, was noch übrig bleibt: sie dürfen Member-Funktionen haben).

C++11 verallgemeinert das Prinzip nun. Hier können beliebige Objekte auf diese Art und Weise initialisiert werden. Der Kompiler generiert einfach die passenden Konstruktoraufrufe. Die neue Syntax ist sogar eindeutiger: es gibt Randfälle (bei Bedarf: http://en.wikipedia.org/wiki/Most_vexing_parse), in denen die Konstruktorsyntax zu Uneindeutigkeiten beim Parsing führen kann. Da kann der Compiler dann nicht mehr unterscheiden, ob er eine Variablendeklaration oder eine Funktionsdeklaration vor sich hat. Mit der Uniform Initialization und der neuen Syntax ist das eindeutig: hier werden Variablen initialisiert.

Was für uns aber interessanter ist: wir müssen uns nicht von der Mächtigkeit des C++-Objektsystems verabschieden. Wie gewohnt können wir ableiten (hier von Shape), Konstruktoren definieren, private Variablen in einer Klasse haben oder mit virtuellen Funktionen arbeiten. So kann unser Beispiel zwei hier beispielsweise eine einheitliche Schnittstelle Shape anbieten und trotzdem von der kurzen Syntax gebrauch machen.