wake-up-neo.com

Warum kann ich kein statisches Nicht-Konstanten-Element oder statisches Array in der Klasse initialisieren?

Warum kann ich kein Nicht-Konstanten-static-Mitglied oder static-Array 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 static Datenmember in der Klasse nicht initialisieren?
  2. Warum kann ich static Arrays in der Klasse nicht initialisieren, auch das const Array?
91
Yishu Fang

Warum kann ich static Datenmember in der Klasse nicht initialisieren?

Der C++ - Standard erlaubt nur die Initialisierung von statischen konstanten Integral- oder Aufzählungstypen innerhalb der Klasse. Dies ist der Grund, warum a initialisiert werden darf, andere nicht.

Referenz:
C++ 03 9.4.2 Statische Datenelemente
§4

Wenn ein statisches Datenelement vom Typ const integral oder 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 angezeigt werden. Das Member muss weiterhin in einem Namespace-Bereich definiert sein, wenn es im Programm verwendet wird und die Namespace-Bereichsdefinition keinen Initialisierer enthält.

Was sind Integraltypen?

C++ 03 3.9.1 Grundtypen
§7

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

Fußnote:

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

Problemumgehung:

Sie können den Aufzählungstrick verwenden, um ein Array in Ihrer Klassendefinition zu 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 treffend hier:

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

Warum sind nur static const Integrale Typen und Aufzählungen für die Klasseninitialisierung zulässig?

Die Antwort ist in Bjarnes Zitat verborgen. Lies es genau durch.
"C++ erfordert, dass jedes Objekt eine eindeutige Definition hat. Diese Regel würde verletzt, wenn C++ die Definition von Entitäten innerhalb der Klasse zulässt, die im Speicher als Objekte gespeichert werden müssen."

Beachten Sie, dass nur static const - Ganzzahlen als Konstanten für die Kompilierungszeit behandelt werden können. Der Compiler weiß, dass sich der ganzzahlige Wert zu keinem Zeitpunkt ändert, und kann daher seine eigene Magie anwenden und Optimierungen anwenden. 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 ist Gibt es solche Variablen die Ausnahme von Regel von Bjarne erwähnt.

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

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


Wie ändert sich das in C++ 11?

C++ 11 lockert die Einschränkung in gewissem Maße.

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 Gleichheitsinitialisierer angeben, in dem jeder Initialisierer steht -Klausel , die ein Zuweisungsausdruck ist, ist ein konstanter Ausdruck. Ein statisches Datenelement vom Typ Literal kann in der Klassendefinition mit constexpr specifier; Deklariert werden. In diesem Fall muss in der Deklaration ein Klammer-oder-Gleichheits-Initialisierer angegeben werden wobei jede Initialisierungsklausel , die ein Zuweisungsausdruck ist, ein konstanter Ausdruck ist. [Hinweis: In beiden Fällen wird das Mitglied möglicherweise in konstanten Ausdrücken angezeigt. —End note] Der Member muss weiterhin in einem Namespace-Bereich definiert sein, wenn er im Programm verwendet wird und die Namespace-Bereichsdefinition keinen Initialisierer enthalten soll.

In C++ 11 kann ein nicht statisches Datenelement dort initialisiert werden, wo es deklariert ist (in seiner Klasse). Dies bedeutet eine sehr einfache Benutzersemantik.

Beachten Sie, dass diese Funktionen in der neuesten Version von gcc 4.7 noch nicht implementiert wurden. Es kann daher zu Kompilierungsfehlern kommen.

125
Alok Save

Dies scheint ein Relikt aus der alten Zeit der einfachen 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 Linker heute (gcc 4.9.2) tatsächlich schlau genug ist.

Lustig: Drucke 0123 am Arm und 3210 auf x86.

3
not-a-user

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

1
Mehrdad