Automatisch Initialisiert

Inhaltsverzeichnis[Anzeigen]

Das wohl am häufigsten verwendete Feature aus C++11 ist das Schlüsselwort auto. Dank auto bestimmt der Compiler den Typ einer Variable direkt aus seinem Initialisierer. Was hat das ganze mit sicherheitskritischer Software zu tun?

 

Schnelldurchlauf mit auto

Automatische Typableitung mit auto ist sehr praktisch. Zum einen erspart sie viel unnötige Tipparbeit oder hilft komplexe Templatetypen in Wohlgefallen aufzulösen, zum andern liegt der Compiler im Gegensatz zum Programmierer nicht falsch. In dem Beispiel stelle ich dem expliziten Typ den vom Compiler automatisch abgeleiteten Typ gegenüber.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <vector>

int myAdd(int a,int b){ return a+b; }

int main(){

  // define an int-value
  int i= 5;                                  // explicit
  auto i1= 5;                                // auto
 
  // define a reference to an int
  int& b= i;                                 // explicit
  auto& b1= i;                               // auto
 
  // define a pointer to a function
  int (*add)(int,int)= myAdd;               // explicit
  auto add1= myAdd;                         // auto
  
  // iterate through a vector
  std::vector<int> vec;
  for (std::vector<int>::iterator it= vec.begin(); it != vec.end(); ++it){} 
  for (auto it1= vec.begin(); it1 != vec.end(); ++it1) {}

}

 

Um den Typ der Variable zu bestimmen, wendet der Compiler die Regeln für die Ableitung der Template-Argumente für Funktions-Templates an (template argument deduction). Das bedeutet insbesondere, das er die äußeren const- oder volatile Qualifier und Referenzen entfernt. Das zeigt das nächste Beispiel für Konstanten und Referenzen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(){
  
  int i= 2011;
  const int i2= 2014;
  const int& i3= i2;
  
  auto a2= i2;     // int
  auto a3= i3;     // int
  
}

 

Wie kann ich mir so sicher sein, dass a2 bzw a3 vom Typ int sind, obwohl sie von einer Variable vom Typ const int bzw. einer Variable vom Typ const int&  initialisiert wurden? Hier lag ich selbst schon ein paar Mal daneben. Die Antwort ist einfach. Ich frage den Compiler. Das nur deklarierte Klassen-Template GetType leistet mir dazu wertvolle Hilfe

template <typename T>
class GetType; 

 

Instanziiere ich ein nur deklariertes Klassen-Template, moniert das Compiler sofort. Ihm fehlt die Definition. Genau diese Eigenschaft nütze ich aus. Freundlicher Weise teilt mir der Compiler exakt den Typ des Klassen-Templates mit, den er nicht instanziieren konnte. Zuerst zum erweiterten Sourcecode. Die Versuche, das nur deklarierte Klassen-Template zu instanziieren, habe ich auskommentiert.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <get_type.hpp>

int main(){
  
  int i= 2011;
  const int i2= 2014;
  // GetType<decltype(i2)> myType;
  const int& i3= i2;
  // GetType<decltype(i3)> myType;
  
  auto a2= i2; 
  // GetType<decltype(a2)> myType;
  auto a3= i3;
  // GetType<decltype(a3)> myType;
  
}

 

Die GetType-Aufrufe in den Zeilen 7,9, 12 und 14 verwenden den Spezifier decltype, mit dessen Hilfe sich genau der deklarierte Typ der Variable bestimmen lässt. Der Rest ist nur noch Fleißarbeit. Ich versuche das Programm viermal zu übersetzen, indem ich sukzessive jeden GetType-Ausdruck einkommentiere. Ein scharfer Blick auf die Fehlermeldungen des g++-Compilers lohnt sich.

 autoGetType

 

Die entscheidendenden Ausdrücke der Fehlermeldungen habe ich in dem Screenshot rot unterstrichen. Beindruckt? Was hat aber das ganze mit den besonderen sicherheitskritischen Anforderungen an embedded Software zu tun?

Initialisiere mich!

auto bestimmt seinen Typ aus seinem expliziten Initialisierer. Das heißt ganz einfach. Ohne Initialisierer gibt es keinen Typ und damit auch keine Variable. Oder anders ausgedrückt. Der Compiler sorgt dafür, dass jeder Typ initialisiert wird. Dies ist ein netter Seiteneffekt von auto, der meines Erachtens viel zu selten betont wird.

Es macht keinen Unterschied, ob einer Variable infolge von Nachlässigkeit oder einem falschen Verständnis der Sprache C++ nicht initialisiert wurde. Das Ergebnis ist das gleiche: undefiniertes Verhalten. Dieser sehr häufige Programmierfehler lässt sich durch den konsequenten Einsatz von auto vermeiden. Den Hand aufs Herz. Kennst du alle Regeln zur Initialisierung von Variablen? Wenn nicht, empfehle ich gerne die Lektüre zur default initialization und alle referenzierten Dokumente. Mir ist es ein Rätsel, warum das Dokument in Anlehnung an den aktuelle C++-Standard davon spricht, das lokale Variablen auf einen unbestimmten Wert initialisiert werden: "objects with automatic storage duration (and their subobjects) are initialized to indeterminate values". Die Formulierung finde ich maximal verwirrend. Für mich werden lokale Variablen nicht initialisiert.

Das zweite Programm zur default initialization habe ich leicht modfiziert. Damit wird ein Teil der undefiniertes Verhalten offensichtlich.

 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
#include <iostream>
#include <string>
 
struct T1 {};
 
struct T2{
    int mem;     // Not ok: indeterminate value
 public:
    T2() {} 
};
 
int n;          //  ok: initialized to 0
 
int main()
{
    int n;               // Not ok: indeterminate value
    std::string s;       // ok: Invocation of the default constructor; initialized to "" 
    T1 t1;               // ok: Invocation of the default constructor 
    T2 t2;               // ok: Invocation of the default constructor
    
    std::cout << "::n " << ::n << std::endl;
    std::cout << "n: " << n << std::endl;
    std::cout << "s: " << s << std::endl;
    std::cout << "T2().mem: " << T2().mem << std::endl;
                      
}

 

Zuerst zu dem Bereichsauflöser :: in Zeile 21. Durch ihn wird der globale Geltungsbereich adressiert. In diesem Fall die Variable n in Zeile 12. Interessanter Weise gibt die automatische Variable n in Zeile 22 den Wert 0 aus, obwohl sie einen unbestimmten Wert besitzt. Soviel zu undefiniertem Verhalten. Da verhält sich die Variable mem der Klasse T2 deutlich berechenbarer Sie gibt tatsächlich einen unbestimmten Wert aus.

 init

Wie schaut das Programm mit Hilfe von auto aus?

 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
#include <iostream>
#include <string>
 
struct T1 {};
 
struct T2{
    int mem= 0;  // auto mem= 0 is an error
 public:
    T2() {}
};
 
auto n= 0;
 
int main(){

    auto n= 0;
    auto s=""s;        
    auto t1= T1();               
    auto t2= T2();
    
    std::cout << "::n " << ::n << std::endl;
    std::cout << "n: " << n << std::endl;
    std::cout << "s: " << s << std::endl;
    std::cout << "T2().mem: " << T2().mem << std::endl;
                      
}

 

Zwei Zeilen sind in dem Sourcecode besonders interessant. Zum einen die Zeile 7. Der aktuelle C++-Standard erlaubt es nicht, nicht-konstante Mitglieder einer Klasse mit auto zu definieren. Daher muss in diesem Fall der Typ int explizit angegebene werden. Zu diesem nicht ganz intuitiven Verhalten gibt es einige Diskussion im Artikel 3897.pdf des C++-Standardisierungskomitees. Zum anderen ist die Zeile 17 besonders interessant. Der C++14-Standard erhielt C++ String-Literale. Diese entstehen dadurch, das ein C-String Literal wie er leere String ("") durch das Suffix s erweitert wird (""s).

Die Ausgabe des Programms ist nicht wirklich spannend. Nur der Vollständigkeit halber. T2().mem besitzt jetzt den Wert 0.

 initAuto

Refaktorierung

Gerade in dem Augenblick, als ich den Artikel abschließen will, fällt mir ein weiterer sehr interessanter Anwendungsfall von auto ein. auto unterstützt sehr gut das Refaktorieren von Code. Zum einen ist es einfach, den Code umzustrukturieren, wenn er keine expliziten Typinformationen enthält. Zum anderen passt der Compiler automatisch alle Typänderungen an. Was meine ich damit? Das kleine Beispiel soll die automatische Typisierung von ganzen Codeabschnitten verdeutlichen. Zuerst das klassische Beispiel ohne automatische Typableitung.

int a= 5;
int b= 10;
int sum=  a * b * 3;
int res= sum + 10; 

 

Ersetze ich die Variable b vom Typ int durch den double Wert 10.5, muß ich die Typen aller von b abhängigen Werte anpassen. Dies ist umständlich und fehleranfällig. Zum einen gilt es, die richtigen Typen zu verwenden, zum andern, eventuelle Überlaufe der Variablen zu berücksichtigen.

int a2= 5;
double b2= 10.5;
double sum2= a2 * b2 * 3;
double res2= sum2 * 10.5;

 

Diese Gefahren sind bei der automatischen Typableitung gebannt. Jetzt geht alles auto-matisch.

 

 

 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
#include <typeinfo>
#include <iostream>

int main(){
  
  std::cout << std::endl;

  auto a= 5;
  auto b= 10;
  auto sum=  a * b * 3;
  auto res= sum + 10; 
  std::cout << "typeid(res).name(): " << typeid(res).name() << std::endl;
  
  auto a2= 5;
  auto b2= 10.5;
  auto sum2= a2 * b2 * 3;
  auto res2= sum2 * 10;  
  std::cout << "typeid(res2).name(): " << typeid(res2).name() << std::endl;
  
  auto a3= 5;
  auto b3= 10;
  auto sum3= a3 * b3 * 3.1f;
  auto res3= sum3 * 10;  
  std::cout << "typeid(res3).name(): " << typeid(res3).name() << std::endl;
  
  std::cout << std::endl;
   
}

 

Die kleine Variation des Codeschnipsel ergibt immer den richtigen Typ für das Ergebnis res, res2 oder auch res3. Dafür steht der Compiler. So ist die Variable b2 in Zeile 15 vom Typ double und damit res2 vom Typ double, so wird sum3 in Zeile 22 durch die Multiplikation mit dem float-Literal 3.1f zum float-Typ und damit auch res3. Um die Typen explizit auszugeben, werde ich den typeid-Operator, der in der Headerdatei typeinfo deklariert ist, verwenden.

Der Screenshot zeigt das Ergebnis schwarz auf weiß.

 autoRefact

Beeindruckt? Ich zu mindestens schon.

Wie geht's weiter?

Die {}-Initialisierung in C++ hat viel mit der automatischen Typableitung gemein. Sie wird ähnlich oft eingesetzt, vereinfacht deutlich das Leben eines Programmierers und trägt dazu bei, den Code sicherer zu machen. Mit ihr geht es im nächsten Artikel weiter.

Was du schon immer wissen wolltest

Automatische Typableitung mit auto und decltype
 Die automatische Typableitung mit auto und decltype stelle ich in meinem Arikel Neue Ausdruckskraft 02/2014 der Artikelserie Modernes C++ in der Praxis genauer vor.

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Hole dir dein E-Book. Unterstütze meinen Blog.

 

Tags: auto

Kommentare   

0 #1 Rod 2016-08-31 15:19
Keep up the wonderful work, I read few posts on this website and I conceive that
your web blog is really interesting and holds circles of great information.
Zitieren

Kommentar schreiben


Modernes C++

Abonniere den Newsletter

Inklusive zwei Artikel meines Buchs
Introduction und Multithreading

Beiträge-Archiv

Sourcecode

Neuste Kommentare