Konstante Ausdrücke mit constexpr

Mit dem Schlüsselwort constexpr lässt sich ein Ausdruck definieren, der zur Compilezeit evaluiert werden kann. constexpr lässt sich für Variablen, Funktionen aber auch benutzerdefinierte Typen verwenden. Ein zur Compilezeit evaluierter Ausdruck besitzt viele Vorteile. So sind constexpr Variablen und auch Instanzen von benutzerdefinierten Typen automatisch thread-sicher und können im billigen ROM gespeichert werden, so werden constexpr Funktionen zur Compilezeit ausgeführt, so dass das Ergebnis der Funktion direkt zur Laufzeit zur Verfügung steht.

Alles zur Compilezeit

In dem Artikel Benutzerdefinierte Literale habe ich es bereits angedeutet. Die Berechnung, wie viele Kilometer ich im Schnitt in der Woche mit dem Auto zurücklege, besitzt deutliches Optimierungspotential. Mein Versprechen löse ich mit diesem Artikel ein. Um den Faden wieder leichter aufzunehmen. Das Programm aus dem Artikel Benutzerdefinierte Literale auf einen Blick.

 

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// userdefinedLiterals.cpp

#include <iostream>

namespace Distance{

  class MyDistance{
    public:
      MyDistance(double i):m(i){}

      friend MyDistance operator+(const MyDistance& a, const MyDistance& b){
        return MyDistance(a.m + b.m);
      }
      friend MyDistance operator-(const MyDistance& a,const MyDistance& b){
        return MyDistance(a.m - b.m);
      }
	  
      friend MyDistance operator*(double m, const MyDistance& a){
        return MyDistance(m*a.m);
}
friend MyDistance operator/(const MyDistance& a, int n){ return MyDistance(a.m/n); } friend std::ostream& operator<< (std::ostream &out, const MyDistance& myDist){ out << myDist.m << " m"; return out; } private: double m; }; namespace Unit{ MyDistance operator "" _km(long double d){ return MyDistance(1000*d); } MyDistance operator "" _m(long double m){ return MyDistance(m); } MyDistance operator "" _dm(long double d){ return MyDistance(d/10); } MyDistance operator "" _cm(long double c){ return MyDistance(c/100); } } } Distance::MyDistance getAverageDistance(std::initializer_list<Distance::MyDistance> inList){ auto sum= Distance::MyDistance{0.0}; for (auto i: inList) sum = sum + i ; return sum/inList.size(); } using namespace Distance::Unit; int main(){ std:: cout << std::endl; auto work= 63.0_km; auto workPerDay= 2 * work; auto abbrevationToWork= 5400.0_m; auto workout= 2 * 1600.0_m; auto shopping= 2 * 1200.0_m; auto distPerWeek1= 4*workPerDay-3*abbrevationToWork+ workout+ shopping; auto distPerWeek2= 4*workPerDay-3*abbrevationToWork+ 2*workout; auto distPerWeek3= 4*workout + 2*shopping; auto distPerWeek4= 5*workout + shopping; auto averageDistance= getAverageDistance({distPerWeek1,distPerWeek2,distPerWeek3,distPerWeek4}); std::cout << "averageDistance: " << averageDistance << std::endl; std::cout << std::endl; }

 

Wie kann das Programm deutlich optimiert werden? Ganz einfach, durch constexpr. Die zentrale Idee ist es, alle Instanzen der Klasse MyDistance im Hauptprogramm als constexpr zu deklarieren. Dadurch fordere ich vom Compiler, die Objekte zur Compilezeit zu instanziieren. Das kann der Compiler aber nur, wenn die Instanziierung auf konstanten Ausdrücken basiert. Falls dies nicht möglich ist, moniert dies der Compiler sofort. 

 

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// userdefinedLiteralsConstexpr.cpp

#include <iostream>

namespace Distance{

  class MyDistance{
    public:
      constexpr MyDistance(double i):m(i){}

      friend constexpr MyDistance operator+(const MyDistance& a, const MyDistance& b){
        return MyDistance(a.m + b.m);
      }
      friend constexpr MyDistance operator-(const MyDistance& a,const MyDistance& b){
        return MyDistance(a.m - b.m);
      }
	  
      friend constexpr MyDistance operator*(double m, const MyDistance& a){
        return MyDistance(m*a.m);
      }
	  
      friend constexpr MyDistance operator/(const MyDistance& a, int n){
        return MyDistance(a.m/n);
      }
	  
      friend std::ostream& operator<< (std::ostream &out, const MyDistance& myDist){
        out << myDist.m << " m";
        return out;
      }
    private:
double m; }; namespace Unit{ constexpr MyDistance operator "" _km(long double d){ return MyDistance(1000*d); } constexpr MyDistance operator "" _m(long double m){ return MyDistance(m); } constexpr MyDistance operator "" _dm(long double d){ return MyDistance(d/10); } constexpr MyDistance operator "" _cm(long double c){ return MyDistance(c/100); } } } constexpr Distance::MyDistance getAverageDistance(std::initializer_list<Distance::MyDistance> inList){ auto sum= Distance::MyDistance{0.0}; for (auto i: inList) sum = sum + i ; return sum/inList.size(); } using namespace Distance::Unit; int main(){ std:: cout << std::endl; constexpr auto work= 63.0_km; constexpr auto workPerDay= 2 * work; constexpr auto abbrevationToWork= 5400.0_m; constexpr auto workout= 2 * 1600.0_m; constexpr auto shopping= 2 * 1200.0_m; constexpr auto distPerWeek1= 4*workPerDay-3*abbrevationToWork+ workout+ shopping; constexpr auto distPerWeek2= 4*workPerDay-3*abbrevationToWork+ 2*workout; constexpr auto distPerWeek3= 4*workout + 2*shopping; constexpr auto distPerWeek4= 5*workout + shopping; constexpr auto averageDistance= getAverageDistance({distPerWeek1,distPerWeek2,distPerWeek3,distPerWeek4}); std::cout << "averageDistance: " << averageDistance << std::endl; std::cout << std::endl; }

 

Das Ergebnis der Berechnung ist nicht besonders spannend. Das Übersetzen des Programms setzt aber einen Compiler voraus, der den C++14 Standard umsetzt. Mit einem aktuellen GCC- oder clang-Compiler ist dies sichergestellt. Der aktuellen Microsoft Visual 2015 C++-Compiler unterstützt constexpr Funktionen nach dem C++11-Standard. Daher scheitert er an der Funktion getAverageDistance. Im C++11-Standard darf eine constexpr Funktion nur aus einer return-Anweisung bestehen.

userdefinedLiteralsConstexprResult

Viel spannender ist es da schon, sich die Assembleranweisungen anzuschauen. Am einfachsten geht es mit dem Interaktiven Compiler, der auf https://gcc.godbolt.org/ gehosted ist. 

userdefinedLiteralsConstexpr

 

Wie ist das Ergebnis zu interpretieren? Ganz einfach. Die in Hauptprogramm (Zeile 64 - 75) definierten konstanten Ausdrücke sind bereits als Konstanten Teil des Assemblerprogramms. Anders ausgedrückt. Alle Berechnung werden zur Compilezeit ausgeführt. Zur Laufzeit liegen nur noch die konstanten Ausdrücke vor. Einfacher kann es für die Laufzeit nicht mehr sein.

Wie geht's weiter?

So, dass soll als Aperitif ausreichend sein. Die Details folgen im nächsten Artikel. In dem Artikel werde ich mir constexpr Variablen, Funktionen und benutzerdefinierte Typen genauer anschauen. Es gilt einige Dinge im Kopf zu behalten. Zum einen sind constexpr Funktionen in C++14 deutlich mächtiger als in C++11, zum anderen können constexpr Funktionen auch zur Laufzeit ausgeführt werden. Darüber gelten für die Methoden von benutzerdefinierten Typen einige Einschränkungen, wollen sie zur Compilezeit instanziiert werden.

 

 

 

 

 

 

 

 

 

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: constexpr

Mentoring

Stay Informed about my Mentoring

 

Rezensionen

Tutorial

Besucher

Heute 553

Gestern 3671

Woche 8835

Monat 27952

Insgesamt 3790045

Aktuell sind 331 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare