Warum kann ich nicht konstante statische Elemente oder statische Arrays in der Klasse nicht initialisieren?

116

Warum kann ich kein nicht konstantes staticMitglied oder staticArray in einer Klasse initialisieren ?

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

Der Compiler gibt folgende Fehler aus:

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member b
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type int [2]’

Ich habe zwei Fragen:

  1. Warum kann ich staticDatenelemente in der Klasse nicht initialisieren ?
  2. Warum kann ich staticArrays in der Klasse nicht initialisieren , auch nicht das constArray?
Yishu Fang
quelle
1
Ich denke, der Hauptgrund ist, dass es schwierig ist, richtig zu machen. Im Prinzip könnten Sie wahrscheinlich das tun, wovon Sie sprechen, aber es würde einige seltsame Nebenwirkungen geben. Wenn Ihr Array-Beispiel zulässig wäre, könnten Sie möglicherweise den Wert von A :: c [0] abrufen, A :: c jedoch nicht an eine Funktion übergeben, da dies eine Adresse und Kompilierungszeit erfordern würde Konstanten haben keine Adresse. C ++ 11 hat einige davon durch die Verwendung von constexpr ermöglicht.
Vaughn Cato
Tolle Frage und Antwort. Link, der mir geholfen hat: msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

Antworten:

144

Warum kann ich staticDatenelemente in der Klasse nicht initialisieren ?

Mit dem C ++ - Standard können nur statische Konstantenintegral- oder Aufzählungstypen innerhalb der Klasse initialisiert werden. Dies ist der Grund, warum adie Initialisierung zulässig ist, während andere dies nicht tun.

Referenz:
C ++ 03 9.4.2 Statische Datenelemente
§4

Wenn ein statisches Datenelement vom Typ const Integral oder vom Typ const enumeration ist, kann seine Deklaration in der Klassendefinition einen Konstanteninitialisierer angeben, der ein integraler konstanter Ausdruck sein soll (5.19). In diesem Fall kann das Element in ganzzahligen konstanten Ausdrücken erscheinen. Das Mitglied muss weiterhin in einem Namespace-Bereich definiert sein, wenn es im Programm verwendet wird, und die Namespace-Bereichsdefinition darf keinen Initialisierer enthalten.

Was sind integrale Typen?

C ++ 03 3.9.1 Grundtypen
§7

Die Typen bool, char, wchar_t sowie die vorzeichenbehafteten und vorzeichenlosen Ganzzahltypen werden zusammen als integrale Typen bezeichnet.43) Ein Synonym für integralen Typ ist der ganzzahlige Typ.

Fußnote:

43) Daher sind die Aufzählungen (7.2) nicht ganzzahlig; Aufzählungen können jedoch auf int, unsigned int, long oder unsigned long heraufgestuft werden, wie in 4.5 angegeben.

Problemumgehung:

Mit dem Enum-Trick können Sie ein Array in Ihrer Klassendefinition initialisieren.

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

Warum erlaubt der Standard dies nicht?

Bjarne erklärt dies hier treffend :

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.

Warum sind nur static constintegrale Typen und Aufzählungen in der Klasseninitialisierung zulässig?

Die Antwort ist in Bjarnes Zitat verborgen. Lesen Sie es genau durch:
"C ++ erfordert, 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."

Beachten Sie, dass nur static constGanzzahlen als Konstanten für die Kompilierungszeit behandelt werden können. Der Compiler weiß, dass sich der ganzzahlige Wert zu keiner Zeit ändert und daher seine eigene Magie anwenden und Optimierungen anwenden kann. Der Compiler fügt solche Klassenmitglieder einfach ein, dh sie werden nicht mehr im Speicher gespeichert, da die Notwendigkeit, im Speicher gespeichert zu werden, beseitigt wird gibt es solchen Variablen die Ausnahme von der von Bjarne erwähnten Regel.

Hierbei ist zu beachten, dass selbst wenn static constIntegralwerte eine In-Class-Initialisierung aufweisen können, die Adressierung solcher Variablen nicht zulässig ist. Man kann die Adresse eines statischen Mitglieds nehmen, wenn (und nur wenn) es eine Definition außerhalb der Klasse hat. Dies bestätigt die obige Argumentation weiter.

Aufzählungen sind zulässig, da Werte eines Aufzählungstyps verwendet werden können, wenn Ints erwartet werden. siehe Zitat oben


Wie ändert sich dies in C ++ 11?

C ++ 11 lockert die Einschränkung bis zu einem gewissen Grad.

C ++ 11 9.4.2 Statische Datenelemente
§3

Wenn ein statisches Datenelement vom Typ const literal ist, kann seine Deklaration in der Klassendefinition einen Klammer-oder-Gleich-Initialisierer angeben, in dem jede Initialisierer-Klausel , die ein Zuweisungsausdruck ist, ein konstanter Ausdruck ist. Ein statisches Datenelement vom Typ Literal kann in der Klassendefinition mit dem constexpr specifier;wenn ja deklariert werden. In seiner Deklaration muss ein Klammer-oder-Gleich-Initialisierer angegeben werden, in dem jede Initialisierer-Klausel ein Zuweisungsausdruck istist ein konstanter Ausdruck. [Hinweis: In beiden Fällen kann das Mitglied in konstanten Ausdrücken erscheinen. —End note] Das Mitglied muss weiterhin in einem Namespace-Bereich definiert sein, wenn es im Programm verwendet wird, und die Definition des Namespace-Bereichs darf keinen Initialisierer enthalten.

Auch C ++ 11 wird ermöglichen (§12.6.2.8) ein nicht-statisches Datenelement initialisiert werden , wo sie (in dieser Klasse) deklariert wird. Dies bedeutet eine sehr einfache Benutzersemantik.

Beachten Sie, dass diese Funktionen in der neuesten Version 4.7 noch nicht implementiert wurden. Daher werden möglicherweise immer noch Kompilierungsfehler angezeigt.

Alok Speichern
quelle
7
In c ++ 11 ist das anders. Die Antwort könnte aktualisiert werden.
Bames53
4
Dies scheint nicht zu stimmen: "Beachten Sie, dass nur statische Konstanten-Ganzzahlen als Kompilierungszeitkonstanten behandelt werden können. Der Compiler weiß, dass sich der Ganzzahlwert zu keiner Zeit ändert, und kann daher seine eigene Magie anwenden und Optimierungen anwenden, der Compiler einfach Inlines solcher Klassenmitglieder, dh sie werden nicht mehr im Speicher gespeichert . " Sind Sie sicher, dass sie nicht unbedingt im Speicher gespeichert sind? Was ist, wenn ich Definitionen für die Mitglieder gebe? Was würde &memberzurückkehren?
Nawaz
2
@Als: Ja. Das ist meine Frage. Warum C ++ die Initialisierung in der Klasse nur für integrale Typen zulässt, wird von Ihrer Antwort nicht richtig beantwortet. Überlegen Sie, warum die Initialisierung für static const char*Mitglieder nicht möglich ist .
Nawaz
3
@Nawaz: Da C ++ 03 nur erlaubt Konstant Initialisierer für statische und const Integral- und const Aufzählungstyp und kein anderer Typ, C ++ 11 erstreckt sich dies zu einer const literal Typ , die die Normen für die In-Class - Initialisierung durch Begrenzung lockert in C ++ 03 war vielleicht ein Versehen, das eine Änderung rechtfertigte und daher in C ++ 11 korrigiert wurde, wenn es traditionelle taktische Gründe für die Änderung gibt, die mir nicht bekannt sind. Wenn Sie wissen, dass Sie sich frei fühlen, diese zu teilen Sie.
Alok Save
4
Die von Ihnen erwähnte "Problemumgehung" funktioniert nicht mit g ++.
Iammilind
4

Dies scheint ein Relikt aus den alten Zeiten einfacher Linker zu sein. Sie können statische Variablen in statischen Methoden als Problemumgehung verwenden:

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

und

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

und

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

bauen:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

Lauf:

./main

Die Tatsache, dass dies funktioniert (konsistent, auch wenn die Klassendefinition in verschiedenen Kompilierungseinheiten enthalten ist), zeigt, dass der heutige Linker (gcc 4.9.2) tatsächlich intelligent genug ist.

Witzig: Druckt 0123auf Arm und 3210x86.

kein Benutzer
quelle
1

Ich denke, es soll Sie daran hindern, Erklärungen und Definitionen zu mischen. (Denken Sie an die Probleme, die auftreten können, wenn Sie die Datei an mehreren Stellen einfügen.)

user541686
quelle
0

Es ist, weil es nur eine Definition von geben kann A::a allen Übersetzungseinheiten verwendet wird.

Wenn Sie static int a = 3;in einer Klasse in einem Header auftreten, der in allen Übersetzungseinheiten enthalten ist, erhalten Sie mehrere Definitionen. Daher wird eine nicht externe Definition einer Statik zwangsweise zu einem Compilerfehler gemacht.

Verwenden static inlineoder static constAbhilfe schaffen. static inlineKonkretisiert das Symbol nur, wenn es in der Übersetzungseinheit verwendet wird, und stellt sicher, dass der Linker nur eine Kopie auswählt und belässt, wenn sie in mehreren Übersetzungseinheiten definiert ist, da sie sich in einer Comdat-Gruppe befindet. constIm Dateibereich gibt der Compiler niemals ein Symbol aus, da es im Code immer sofort ersetzt wird, es sei dennextern es nicht verwendet wird, was in einer Klasse nicht zulässig ist.

Eine zu beachtende Sache static inline int b;ist, dass sie als Definition behandelt wird, während static const int boder static const A b;immer noch als Deklaration behandelt wird und außerhalb der Zeile definiert werden muss, wenn Sie sie nicht innerhalb der Klasse definieren. Interessanterweise static constexpr A b;wird es als Definition behandelt, während static constexpr int b;es sich um einen Fehler handelt und einen Initialisierer haben muss (dies liegt daran, dass sie jetzt zu Definitionen werden und wie jede const / constexpr-Definition im Dateibereich einen Initialisierer benötigen, den ein int nur über einen Klassentyp verfügt funktioniert, weil es implizit ist, = A()wenn es sich um eine Definition handelt - clang erlaubt dies, aber gcc erfordert, dass Sie explizit initialisieren, oder es ist ein Fehler. Dies ist kein Problem mit Inline).static const A b = A();ist nicht erlaubt und muss constexproder seininlineum einen Initialisierer für ein statisches Objekt mit Klassentyp zuzulassen, dh ein statisches Element vom Klassentyp mehr als eine Deklaration zu machen. Ja, in bestimmten Situationen A a;ist dies nicht dasselbe wie explizites Initialisieren A a = A();(Ersteres kann eine Deklaration sein, aber wenn nur eine Deklaration für diesen Typ zulässig ist, ist Letzteres ein Fehler. Letzteres kann nur für eine Definition verwendet werden. constexprMacht es zu einer Definition ). Wenn Sie constexpreinen Standardkonstruktor verwenden und angeben, muss dies der Konstruktor seinconstexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

Ein statisches Element ist eine vollständige Deklaration des Dateibereichs extern int A::a;(die nur in der Klasse erstellt werden kann und Definitionen außerhalb der Zeile müssen sich auf ein statisches Element in einer Klasse beziehen und müssen Definitionen sein und dürfen kein externes Element enthalten), während ein nicht statisches Element Teil von ist die vollständige Typdefinition einer Klasse und haben die gleichen Regeln wie Dateibereichsdeklarationen ohne extern. Sie sind implizit Definitionen. So int i[]; int i[5];ist eine Neudefinition der Erwägung , dass static int i[]; int A::i[5];nicht aber im Gegensatz zu 2 Externen, wird der Compiler noch ein Duplikat Mitglied erkennen , wenn Sie tun static int i[]; static int i[5];in der Klasse.

Lewis Kelsey
quelle
-3

statische Variablen sind klassenspezifisch. Konstruktoren initialisieren Attribute ESPECIALY für eine Instanz.


quelle