Warum kann ich in einer Klasse kein nicht integrales statisches const-Element haben?

72

Ich habe festgestellt, dass C ++ Folgendes nicht kompiliert:

class No_Good {
  static double const d = 1.0;
};

Es wird jedoch gerne eine Variation zulassen, bei der das Double in einen int, unsigned oder einen beliebigen integralen Typ geändert wird:

class Happy_Times {
  static unsigned const u = 1;
};

Meine Lösung bestand darin, es zu ändern, um zu lesen:

class Now_Good {
  static double d() { return 1.0; }
};

und denke, dass der Compiler klug genug sein wird, um bei Bedarf zu inline ... aber es hat mich neugierig gemacht.

Warum erlauben mir die C ++ - Designer, einen int oder unsigned, aber kein double statisch zu konstituieren?

Bearbeiten: Ich verwende Visual Studio 7.1 (.net 2003) unter Windows XP.

Edit2:

Die Frage wurde beantwortet, aber zum Abschluss der Fehler, den ich sah:

error C2864: 'd' : only const static integral data members can be initialized inside a class or struct
Jeffrey Martinez
quelle
Welcher Compiler / welche Plattform oder sehen Sie es auf mehreren?
Warren
Welche Fehlermeldung erhalten Sie in VS7.1?
Jesse Beder

Antworten:

49

Das Problem ist, dass der Compiler bei einer Ganzzahl normalerweise nie eine Speicheradresse für die Konstante erstellen muss. Es existiert zur Laufzeit nicht und jede Verwendung wird in den umgebenden Code eingefügt. Es kann weiterhin entscheiden, ihm einen Speicherort zuzuweisen - falls seine Adresse jemals verwendet wird (oder wenn sie als konstante Referenz an eine Funktion übergeben wird), muss dies der Fall sein. Um ihm eine Adresse zu geben, muss sie in einer Übersetzungseinheit definiert werden. In diesem Fall müssen Sie die Deklaration von der Definition trennen, da sie sonst in mehreren Übersetzungseinheiten definiert wird.

Bei Verwendung von g ++ ohne Optimierung ( -O0) werden automatisch konstante Ganzzahlvariablen, jedoch keine konstanten Doppelwerte eingefügt. Bei höheren Optimierungsstufen (z. B. -O1) werden konstante Doppelwerte eingefügt. Daher wird der folgende Code kompiliert, -O1aber NICHT um -O0:

// File a.h
class X
{
 public:
  static const double d = 1.0;
};

void foo(void);

// File a.cc
#include <stdio.h>

#include "a.h"

int main(void)
{
  foo();
  printf("%g\n", X::d);

  return 0;
}

// File b.cc
#include <stdio.h>

#include "a.h"

void foo(void)
{
  printf("foo: %g\n", X::d);
}

Befehlszeile:

g++ a.cc b.cc -O0 -o a   # Linker error: ld: undefined symbols: X::d
g++ a.cc b.cc -O1 -o a   # Succeeds

Für maximale Portabilität sollten Sie Ihre Konstanten in Header-Dateien deklarieren und einmal in einer Quelldatei definieren. Ohne Optimierung wird die Leistung nicht beeinträchtigt, da Sie sowieso nicht optimieren. Wenn jedoch Optimierungen aktiviert sind, kann dies die Leistung beeinträchtigen, da der Compiler diese Konstanten nicht mehr in andere Quelldateien einbinden kann, es sei denn, Sie aktivieren die Optimierung des gesamten Programms. .

Adam Rosenfield
quelle
8
statisches const double d = 1,0; ist nicht gültig C ++. es sollte überhaupt nicht kompiliert werden. Dieses Formular ist nur für ganzzahlige Typen zulässig. Aus diesem Grund sind min und max in numeric_limits <T> Funktionen und keine statischen Konstanten. Ich bin mir nicht sicher, warum es für Sie kompiliert.
Johannes Schaub - Litb
3
es scheint eine gcc-erweiterung zu sein. Kompilieren mit -pedantischen Ausbeuten: "foo.cpp: 4: Fehler: ISO C ++ verbietet die Initialisierung der Mitgliedskonstante 'd' vom nichtintegralen Typ 'const double'"
Johannes Schaub - litb
@ JohannesSchaub-litb: Danke für die Untersuchung. Diese Antwort sollte oben bearbeitet werden, um dies zu signalisieren.
v.oddou
Was bedeutet "Das Problem ist, dass der Compiler bei einer Ganzzahl normalerweise nie eine Speicheradresse für die Konstante erstellen muss. Sie existiert zur Laufzeit nicht und wird bei jeder Verwendung in den umgebenden Code eingefügt . " ? Können Sie mehr Licht ins Dunkel bringen?
Ujjwal Aryan
the compiler usually doesn't have to ever create a memory address for the constant: Würde die Verwendung der Adresse einer Konstanten und das Speichern in einer Variablen den Compiler als Gedankenexperiment zwingen, Speicher für die Konstante zu erstellen?
Kevinarpe
18

Ich sehe keinen technischen Grund warum

struct type {
    static const double value = 3.14;
};

ist verboten. Jeder Anlass, bei dem Sie feststellen, wo es funktioniert, ist auf nicht portierbare implementierungsdefinierte Funktionen zurückzuführen. Sie scheinen auch nur von begrenztem Nutzen zu sein. Für in Klassendefinitionen initialisierte Integralkonstanten können Sie sie verwenden und als Nicht-Typ-Argumente an Vorlagen übergeben und als Größe der Array-Dimensionen verwenden. Für Gleitkommakonstanten ist dies jedoch nicht möglich. Das Zulassen von Gleitkomma-Vorlagenparametern würde eigene Regeln mit sich bringen, die die Mühe nicht wirklich wert sind.

Die nächste C ++ - Version erlaubt dies jedoch mit constexpr:

struct type {
    static constexpr double value = 3.14;
    static constexpr double value_as_function() { return 3.14; }
};

Und wird type::valueeinen konstanten Ausdruck machen. In der Zwischenzeit ist es am besten, dem Muster zu folgen, das auch verwendet wird von std::numeric_limits:

struct type {
    static double value() { return 3.14; }
};

Es wird kein konstanter Ausdruck zurückgegeben (Wert ist zur Kompilierungszeit nicht bekannt), aber das ist nur theoretisch wichtig, da der Wert in der Praxis sowieso eingefügt wird. Siehe den constexpr- Vorschlag. Es beinhaltet

4.4

Floating-point constant expressions

Traditionell ist die Bewertung des konstanten Ausdrucks des Fließkommas zur Kompilierungszeit ein heikles Thema. Aus Gründen der Einheitlichkeit und Allgemeinheit empfehlen wir, Konstantausdrucksdaten von Fließkommatypen zuzulassen, die mit konstanten Fließkommaausdrücken initialisiert werden. Dies erhöht auch die Kompatibilität mit C99 [ISO99, §6.6], was dies ermöglicht

[# 5] Ein Ausdruck, der als Konstante ausgewertet wird, ist in mehreren Kontexten erforderlich. Wenn ein schwebender Ausdruck in der Übersetzungsumgebung ausgewertet wird, müssen die arithmetische Genauigkeit und der Bereich mindestens so groß sein, als ob der Ausdruck in der Ausführungsumgebung ausgewertet würde.

Johannes Schaub - litb
quelle
Ich habe nicht ganz verstanden, dass das Zulassen von Gleitkomma-Vorlagenparametern eigene Regeln mit sich bringt, die die Mühe nicht wirklich wert sind. Es fällt auch schwer, "Wenn ein schwebender Ausdruck in der Übersetzungsumgebung ausgewertet wird, müssen die arithmetische Genauigkeit und der Bereich mindestens so groß sein, als ob der Ausdruck in der Ausführungsumgebung ausgewertet würde."
Chubsdad
2
@Chubsdad Ersteres ist auf die übliche Ungenauigkeit von Gleitkommaberechnungen zurückzuführen, die von Implementierung zu Implementierung unterschiedlich sein können. Während 1+1es sich 2bei jeder Implementierung immer um eine Implementierung handelt, können ähnlich einfache Berechnungen mit Gleitkomma-Mathematik auf verschiedenen Gleitkommamodellen unterschiedliche Ergebnisse liefern (wohlgemerkt, ich mag diese Probleme nicht, aber ich weiß, dass sie existieren).
Johannes Schaub - litb
Für das letztere Problem kenne ich die Gründe nicht wirklich. Beachten Sie, dass sich eine Kompilierungsumgebung von der Ausführungsumgebung für Cross-Compiler unterscheiden kann. Der Grund hierfür ist möglicherweise, sicherzustellen, dass eine große Genauigkeit nicht durch die Berechnung des Ergebnisses zur Kompilierungszeit verschlechtert wird. Aber vielleicht können Sie daraus eine separate SO-Frage machen.
Johannes Schaub - litb
1
Darüber hinaus kann ein kompilierter Code (z. B. armv5) auf mehreren CPUs ausgeführt werden. z.B. eine ArmV7-CPU. Welches könnte verschiedene (zwischengeschaltete) schwebende Präzisionen implementieren. PowerPC ist ein Beispiel für eine CPU, die sich anders als Intel verhält. Einige alte Informationen (P6-Ära) hatten einige Fehler ... Selbst für nur eine Binärdatei kann die Ausführung unterschiedlich sein. Das Standardkomitee befürchtete diese Dose Würmer und beschloss, static floats auszuschließen , was für mich sinnvoll ist.
v.oddou
Hinweis DR 1826 erklärt, warum diese Einschränkung für C ++ 11 beibehalten wurde.
Shafik Yaghmour
7

Es gibt nicht wirklich eine Begründung, aber hier ist, was Stroustrup dazu in "The C ++ Programming Language Third Edition" zu sagen hat:

10.4.6.2 Mitgliedskonstanten

Es ist auch möglich, ein statisches integrales konstantes Element zu initialisieren, indem der Elementdeklaration ein Initialisierer mit konstantem Ausdruck hinzugefügt wird . Zum Beispiel:

class Curious {
    static const int c1 = 7;        // ok, but remember definition
    static int c2 = 11;             // error: not const
    const int c3 = 13;              // error: not static
    static const int c4 = f(17);    // error: in-class initializer not constant
    static const float c5 = 7.0;    // error: in-class not integral
    // ...
};

Ein initialisiertes Mitglied muss jedoch noch irgendwo (eindeutig) definiert sein, und der Initialisierer darf nicht wiederholt werden:

const int Curious::c1;  // necessary, but don't repeat initializer here

Ich halte das für eine Fehlfunktion. Wenn Sie eine symbolische Konstante in einer Klassendeklaration benötigen, verwenden Sie einen Enumerator (4.8, 14.4.6, 15.3). Zum Beispiel:

class X {
    enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 };
    // ...
};

Auf diese Weise wird an keiner anderen Stelle eine Elementdefinition benötigt, und Sie sind nicht versucht, Variablen, Gleitkommazahlen usw. zu deklarieren.

Und in Anhang C (Technische Daten) in Abschnitt C.5 (Konstante Ausdrücke) hat Stroustrup Folgendes über "Konstante Ausdrücke" zu sagen:

An Stellen wie Array-Grenzen (5.2), Fallbezeichnungen (6.3.2) und Initialisierern für Enumeratoren (4.8) erfordert C ++ einen konstanten Ausdruck . Ein konstanter Ausdruck wird zu einer Integral- oder Aufzählungskonstante. Ein solcher Ausdruck besteht aus Literalen (4.3.1, 4.4.1, 4.5.1), Enumeratoren (4.8) und Konstanten, die durch konstante Ausdrücke initialisiert werden. In einer Vorlage kann auch ein ganzzahliger Vorlagenparameter verwendet werden (C.13.3). Floating Literals (4.5.1) können nur verwendet werden, wenn sie explizit in einen integralen Typ konvertiert wurden. Funktionen, Klassenobjekte, Zeiger und Referenzen können nur als Operanden für den Operator sizeof (6.2) verwendet werden.

Intuitive Ausdrücke sind intuitiv einfache Ausdrücke, die vom Compiler ausgewertet werden können, bevor das Programm verknüpft wird (9.1) und gestartet wird.

Beachten Sie, dass er Gleitkomma so gut wie weglässt, weil er in 'konstanten Ausdrücken' spielen kann. Ich vermute, dass Gleitkomma von diesen Arten von konstanten Ausdrücken weggelassen wurde, nur weil sie nicht "einfach" genug sind.

Michael Burr
quelle
c ++ 1x behebt diese Fehlfunktion: (aktueller Entwurf): "Ein Objekt oder eine nicht überladene Funktion, deren Name als potenziell ausgewerteter Ausdruck angezeigt wird, wird verwendet, es sei denn, es handelt sich um ein Objekt, das die Anforderungen für das Anzeigen in einem konstanten Ausdruck erfüllt." Statik muss nur definiert werden, wenn sie verwendet wird, damit dies
gelöst wird
Das Bereitstellen von Definitionen in den meisten heutigen Implementierungen funktioniert aus diesem Grund: "Wenn ein Programm einen Verstoß gegen eine Regel enthält, für die keine Diagnose erforderlich ist, stellt diese Internationale Norm keine Anforderungen an Implementierungen in Bezug auf dieses Programm."
Johannes Schaub - Litb
Die Regel, nach der Sie eine Definition angeben müssen, ist mit "Keine Diagnose erforderlich" gekennzeichnet. Daher gehen die meisten Kompilierungen (einschließlich Comeau) nur in Ordnung, es sei denn, Sie nehmen die Adresse des Objekts und das Objekt wird nicht innerhalb der Klassendefinition initialisiert. Zu diesem Zeitpunkt tritt ein Linkerfehler auf.
Johannes Schaub - litb
2
OK. Heute habe ich herausgefunden, dass c ++ 03 die Regeln bereits geändert hat und es erlaubt, die Definition wegzulassen, wenn die Variable dort erscheint, wo ein integraler konstanter Ausdruck erforderlich ist. gut zu wissen :)
Johannes Schaub - litb
1
@litb: bist du sicher? - C ++ 03 9.4.2 In Absatz 4 scheint immer noch klar zu sein, dass genau eine Definition existieren muss (wenn das Mitglied verwendet wird).
Michael Burr
4

Ich weiß nicht, warum es ein Double anders als ein Int behandeln würde. Ich dachte, ich hätte dieses Formular schon einmal benutzt. Hier ist eine alternative Problemumgehung:

class Now_Better
{
    static double const d;
};

Und in Ihrer CPP-Datei:

double const Now_Better::d = 1.0;
Mark Ransom
quelle
Ja, ich habe mich nur deshalb von dieser möglichen Lösung abgewandt, weil ich dachte, meine sei etwas leichter zu lesen. Ich mag es nicht , einen Wert in separaten Dateien deklarieren und initialisieren zu müssen (Meine Klasse ist in .h). Vielen Dank.
Jeffrey Martinez
0

Hier ist mein Verständnis, das auf Stroustrups Aussage zur Definition in der Klasse basiert

Eine Klasse wird normalerweise in einer Header-Datei deklariert, und eine Header-Datei ist normalerweise in vielen Übersetzungseinheiten enthalten. Um jedoch komplizierte Linkerregeln zu vermeiden, erfordert C ++, dass jedes Objekt eine eindeutige Definition hat. Diese Regel würde verletzt, wenn C ++ die Definition von Entitäten in der Klasse zulässt, die als Objekte im Speicher gespeichert werden müssen.

http://www.stroustrup.com/bs_faq2.html#in-class

Im Grunde ist dies nicht zulässig, da C ++ dies nicht zulässt. Um die Linker-Regeln zu vereinfachen, erfordert C ++, dass jedes Objekt eine eindeutige Definition hat.

Das statische Element hat nur eine Instanz im Klassenbereich, nicht wie reguläre statische Variablen, die in C häufig verwendet werden und nur eine Instanz innerhalb einer Übersetzungseinheit haben.

Wenn statisches Element in der Klasse definiert ist und die Klassendefinition in vielen Übersetzungseinheiten enthalten ist, muss der Linker mehr Arbeit leisten, um zu entscheiden, welches statische Element als einziges in allen zugehörigen Übersetzungseinheiten verwendet werden soll.

Bei regulären statischen Variablen können sie jedoch nur innerhalb einer Übersetzungseinheit verwendet werden. Selbst wenn unterschiedliche statische Variablen in unterschiedlichen Übersetzungseinheiten mit demselben Namen verwendet werden, wirken sie sich nicht gegenseitig aus. Linker kann einfache Arbeit leisten, um reguläre statische Variablen innerhalb einer Übersetzungseinheit zu verknüpfen.

Um die Komplikationen zu verringern und die Basisfunktion bereitzustellen, bietet C ++ die einzige klasseninterne Definition für eine statische Konstante vom Integral- oder Aufzählungstyp.

Liu Nick
quelle