const vs constexpr für Variablen


303

Gibt es einen Unterschied zwischen den folgenden Definitionen?

const     double PI = 3.141592653589793;
constexpr double PI = 3.141592653589793;

Wenn nicht, welcher Stil wird in C ++ 11 bevorzugt?



Beide sind Kompilierungszeitkonstanten. Sie können jedoch einen const_cast des ersten erstellen und darauf schreiben. Es wird jedoch von jedem Compiler optimiert, da dies keinen Einfluss auf "Lesevorgänge" hat, wie sie zur Kompilierungszeit auftreten.
Bonita Montero

Antworten:


347

Ich glaube, es gibt einen Unterschied. Benennen wir sie um, damit wir leichter darüber sprechen können:

const     double PI1 = 3.141592653589793;
constexpr double PI2 = 3.141592653589793;

Beide PI1und PI2sind konstant, dh Sie können sie nicht ändern. Es ist jedoch nur PI2 eine Kompilierungszeitkonstante. Es wird zur Kompilierungszeit initialisiert. PI1kann zur Kompilierungs- oder Laufzeit initialisiert werden. Darüber hinaus kann nur PI2 in einem Kontext verwendet werden, der eine Kompilierungszeitkonstante erfordert. Zum Beispiel:

constexpr double PI3 = PI1;  // error

aber:

constexpr double PI3 = PI2;  // ok

und:

static_assert(PI1 == 3.141592653589793, "");  // error

aber:

static_assert(PI2 == 3.141592653589793, "");  // ok

Was solltest du verwenden? Verwenden Sie, was Ihren Anforderungen entspricht. Möchten Sie sicherstellen, dass Sie eine Kompilierungszeitkonstante haben, die in Kontexten verwendet werden kann, in denen eine Kompilierungszeitkonstante erforderlich ist? Möchten Sie es mit einer zur Laufzeit durchgeführten Berechnung initialisieren können? Usw.


60
Bist du sicher? Weil const int N = 10; char a[N];funktioniert und Array-Grenzen Konstanten zur Kompilierungszeit sein müssen.
Fredoverflow

10
Ich bin mir sicher, was die Beispiele angeht, die ich geschrieben habe (jedes vor dem Posten getestet). Mein Compiler lässt mich jedoch PI1zur Verwendung in einem Array in eine Integralkonstante zur Kompilierungszeit konvertieren , jedoch nicht zur Verwendung als integraler Vorlagenparameter ohne Typ. Die Konvertierbarkeit PI1zur Kompilierungszeit in einen integralen Typ scheint mir also ein kleiner Hit & Miss zu sein.
Howard Hinnant

34
@FredOverflow: Nicht-konstante Array-Indizes haben ungefähr ein Jahrzehnt lang "funktioniert" (es gibt zum Beispiel eine g ++ - Erweiterung dafür), aber das bedeutet nicht, dass es streng legal C ++ ist (obwohl ein neuerer C- oder C ++ - Standard es legal gemacht hat , ich vergessen welche). Was die Unterschiede bei den Kompilierungszeitkonstanten betrifft, so sind Vorlagenparameter und die Verwendung als enumInitialisierer die einzigen zwei bemerkenswerten Unterschiede zwischen constund constexpr(und keiner von beiden funktioniert doubletrotzdem).
Damon

17
Absatz 4 von 5.19 Konstante Ausdrücke [expr.const] ist auch eine (nicht normative) Anmerkung, die bekanntermaßen umreißt, dass eine Implementierung Gleitkomma-Arithmetik zur Kompilierungszeit anders ausführen darf (z. B. in Bezug auf die Genauigkeit) als zur Laufzeit. Also 1 / PI1und 1 / PI2kann zu unterschiedlichen Ergebnissen führen. Ich denke jedoch nicht, dass diese Technik so wichtig ist wie der Rat in dieser Antwort.
Luc Danton

4
Aber es constexpr double PI3 = PI1;funktioniert richtig für mich. (MSVS2013 CTP). Was mache ich falsch?
NuPagadi

77

Kein Unterschied, aber es ist wichtig, wenn Sie einen Typ haben, der einen Konstruktor hat.

struct S {
    constexpr S(int);
};

const S s0(0);
constexpr S s1(1);

s0ist eine Konstante, verspricht jedoch nicht, zur Kompilierungszeit initialisiert zu werden. s1ist markiert constexpr, es ist also eine Konstante, und da Sder Konstruktor ebenfalls markiert ist constexpr, wird er zur Kompilierungszeit initialisiert.

Meistens ist dies wichtig, wenn die Initialisierung zur Laufzeit zeitaufwändig wäre und Sie diese Arbeit auf den Compiler übertragen möchten, wo sie ebenfalls zeitaufwändig ist, aber die Ausführungszeit des kompilierten Programms nicht verlangsamt


3
Ich stimme zu: Die Schlussfolgerung, zu der ich gekommen bin, war, dass constexprzu einer Diagnose führen würde, falls die Berechnung des Objekts zur Kompilierungszeit unmöglich sein sollte. Weniger klar ist, ob eine Funktion, die einen konstanten Parameter erwartet , zur Kompilierungszeit ausgeführt werden kann, sollte der Parameter als constund nicht wie folgt deklariert werden constexpr: dh würde constexpr int foo(S)zur Kompilierungszeit ausgeführt, wenn ich aufrufe foo(s0)?
Matthieu M.

4
@MatthieuM: Ich bezweifle, ob foo(s0)es zur Kompilierungszeit ausgeführt wird, aber Sie wissen nie: Ein Compiler darf solche Optimierungen vornehmen . Weder gcc 4.7.2 noch clang 3.2 erlauben es mir,constexpr a = foo(s0);
rici

50

constexpr gibt einen Wert an, der konstant ist und während der Kompilierung bekannt ist.
const gibt einen Wert an, der nur konstant ist; Es ist nicht obligatorisch, dies während der Kompilierung zu wissen.

int sz;
constexpr auto arraySize1 = sz;    // error! sz's value unknown at compilation
std::array<int, sz> data1;         // error! same problem

constexpr auto arraySize2 = 10;    // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr

Beachten Sie, dass const nicht die gleiche Garantie wie constexpr bietet, da const-Objekte nicht mit Werten initialisiert werden müssen, die während der Kompilierung bekannt sind.

int sz;
const auto arraySize = sz;       // fine, arraySize is const copy of sz
std::array<int, arraySize> data; // error! arraySize's value unknown at compilation

Alle constexpr-Objekte sind const, aber nicht alle const-Objekte sind constexpr.

Wenn Sie möchten, dass Compiler sicherstellen, dass eine Variable einen Wert hat, der in Kontexten verwendet werden kann, in denen Konstanten zur Kompilierungszeit erforderlich sind, müssen Sie nach constexpr und nicht nach const greifen.


2
Ihre Erklärung hat mir sehr gut gefallen. Können Sie bitte mehr dazu sagen? Wo sind die Fälle, in denen wir möglicherweise Kompilierungszeitkonstanten in realen Szenarien verwenden müssen?
Mayukh Sarkar

1
@ MayukhSarkar Einfach Google C ++ warum constexpr , zB stackoverflow.com/questions/4748083/…
underscore_d

18

Einer constexpr-Symbolkonstante muss ein Wert zugewiesen werden, der zur Kompilierungszeit bekannt ist. Zum Beispiel:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    constexpr int c2 = n+7;   // Error: we don’t know the value of c2
    // ...
}

Um Fälle zu behandeln , in denen der Wert einer „Variable“ , die mit einem Wert initialisiert wird, der nicht bei der Kompilierung bekannt ist , aber nie ändert nach der Initialisierung, C ++ bietet eine zweite Form konstant (a const ). Beispielsweise:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    const int c2 = n+7; // OK, but don’t try to change the value of c2
    // ...
    c2 = 7; // error: c2 is a const
}

Solche " const- Variablen" sind aus zwei Gründen sehr häufig:

  1. C ++ 98 hatte kein constexpr, daher verwendeten die Leute const .
  2. Listenelemente „Variablen“, die keine konstanten Ausdrücke sind (ihr Wert ist zum Zeitpunkt der Kompilierung nicht bekannt), aber nach der Initialisierung keine Werte ändern, sind an sich sehr nützlich.

Referenz: "Programmierung: Prinzipien und Praxis mit C ++" von Stroustrup


25
Vielleicht hätten Sie erwähnen sollen, dass der Text in Ihrer Antwort wörtlich aus "Programmieren: Prinzipien und Praxis mit C ++" von Stroustrup
Aky
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.