Dieses AUTO ist cool (Teil 2)

Wir wissen nun, dass Großvater C sein auto nie benutzt hat und es in der Garage verrosten ließ.

Doch sein Enkel C++ 11, ein bekennender auto-Freak, zerlegte die Karosserie, entfernte die Spuren des Zahns der Zeit und baute sich daraus einen neuen Boliden, der jedoch bald ein Eigenleben entwickelte.

Dieser wurde zu einer Rennsau, die bei fast jedem Wettrennen als Nummer 1 über die Ziellinie sauste
… vorausgesetzt: Es saß der richtige Fahrer hinterm Lenkrad.


Wenn man unter C++ weiter “klassisch” programmiert, könnte man denken, dass man auto nicht braucht.
Es ist sogar übersichtlicher, wenn man Datentypen selbst ausschreibt (int x = 42;) anstatt alles von auto erledigen zu lassen (auto x = 42;).

Doch spätestens, wenn man mit Templates und Spezialisierungen arbeitet, sind Typen bei der Deklaration nicht mehr zwingend vorhersehbar.
Doch der Compiler weiß bei der Instanziierung ganz genau, mit welchen Typen er arbeitet, und genau hier wird auto zum gefeierten Heilsbringer.

 1template<class T> struct next_greater_type
 2{
 3  typedef long double type_t;        /* finally the "greatest" type */
 4};
 5
 6template<> struct next_greater_type<char>
 7{
 8  typedef short type_t;
 9};
10
11template<> struct next_greater_type<short>
12{
13  typedef long type_t;
14};
15
16template<> struct next_greater_type<long>
17{
18  typedef long long type_t;
19};
20
21template<> struct next_greater_type<long long>
22{
23  typedef double type_t;
24};
25
26
27template<class T>
28typename next_greater_type<T>::type_t multiply(T const& t1, int t2)
29{
30  typedef typename next_greater_type<T>::type_t greater_type_t;
31        
32  greater_type_t a = greater_type_t(t1);
33  greater_type_t b = greater_type_t(t2);
34        
35  greater_type_t c = a * b;
36  return c;
37}
38
39int main()
40{
41  char c = 127;
42  short s = 32767;
43  long l = 2147483647L;
44  long long ll = 9223372036854775807LL;
45
46  auto result1 = multiply(c, 4);  // short result
47  auto result2 = multiply(s, 4);  // long result
48  auto result3 = multiply(l, 4);  // long long result
49  auto result4 = multiply(ll, 4); // double result
50        
51  return 0;
52}

Im Beispiel wird für jeden Zahlentypen der nächst größere festgelegt. Die generische “Multiplikation” soll so einen Ausgabetypen festlegen, der ohne Verlust das Ergebnis enthalten kann.

Zugegeben, das Beispiel ist wirklich sehr exemplarisch und wenig praxisnah. Aber es zeigt, dass auto einem den Aufwand abnimmt, stets über den template typedef den Ergebnistypen selbst zu bestimmen.

auto und R-Value Referenzen

Seine größte Stärke spielt auto meines Erachtens mit auto&& aus.

Denn wir Programmierer machen gerne den Fehler durch ungenaue und schlecht passende Datentypenangaben Optimierungen zu verhindern.

Gibt eine Funktion eine const-Referenz zurück, sollte diese auch genau so entgegengenommen werden. Nimmt man sie als neue Instanz an, entsteht eine (oft sinnlose) Kopie, die Speicher und CPU-Zeit frisst.

1std::string const& get_text();
2...
3std::string x = get_text();        // falsch!
4std::string const& y = get_text(); //richtig!

Genau hier hilft uns auto&&, denn dieses Konstrukt garantiert, dass die Variable den perfekten Typen erhält.

 1std::string get_copy();
 2std::string& get_reference();
 3std::string const& get_const_reference();
 4std::string&& get_rv_reference();
 5
 6void lame_foo()
 7{
 8  auto a = get_copy();            // std::string / copy
 9  auto b = get_reference();       // std::string / copy
10  auto c = get_const_reference(); // std::string / copy
11  auto d = get_rv_reference();    // std::string / move
12}
13void smart_foo()
14{
15  auto&& a = get_copy();            // std::string&&
16  auto&& b = get_reference();       // std::string&
17  auto&& c = get_const_reference(); // std::string const&
18  auto&& d = get_rv_reference();    // std::string&&
19}

In lame_foo löst auto stets einen std::string aus, was mit Ausnahme der letzten Zeile immer ein Kopieren des Strings erfordert.

smart_foo hingegen nutzt std::string&& um Kopien durch Move-Konstruktion abzufangen und im Fall von “gewöhnlichen” Referenzen werden diese erkannt und als eben solche ohne Performanceverlust entgegengenommen.
Und das ist kurz gesagt: Perfekt.

Scott Meyers bezeichnet diese Nutzung von Referenzen übrigens gerne als Universal References.

Fazit

auto zählt im modernen C++ ganz klar zu meinen Favoriten.
Mehr kann man dazu gar nicht sagen.

Mein Problem in der Entwicklung ist allerdings, dass ich aus Rücksicht auf ältere Compiler (z.B. für Windows CE ), immer noch oft auf dieses Feature verzichte.

Doch bei neuen Projekten ist auto absolut Pflicht!