Dlaczego nie mogę zainicjalizować statycznego członka lub tablicy statycznej w klasie?

Dlaczego nie mogę zainicjalizować non - const static member lub static array w klasie?

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;
}

Kompilator wyświetla następujące błędy:

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]’

Mam dwa pytania:

  1. Dlaczego nie mogę zainicjować static danych członków w klasie?
  2. Dlaczego nie mogę zainicjalizować tablic static w klasie, nawet tablicy const?
Author: Ben, 2012-03-11

5 answers

Dlaczego nie mogę zainicjować static Danych członków w klasie?

Standard C++ pozwala na inicjalizację wewnątrz klasy tylko statycznych typów całkowych lub wyliczeniowych. Jest to powód, dla którego a może być inicjowana, podczas gdy inne nie.

Odniesienie:
C++03 9.4.2 static data members
§4

Jeśli statyczny element danych jest typu const integral lub const enumeration, jego deklaracja w definicji klasy może określać stała-inicjalizator będący całkowym wyrażeniem stałej (5.19). W takim przypadku element może występować w całkowych wyrażeniach stałych. Element musi być nadal zdefiniowany w zakresie przestrzeni nazw, jeśli jest używany w programie, a definicja zakresu przestrzeni nazw nie powinna zawierać inicjatora.

Czym są typy całkowe?

C++03 3.9.1 podstawowe typy
§7

Typy bool, char, wzar_t i podpisane a niepodpisane typy całkowite są zbiorczo nazywane typami całkowymi.43) synonimem typu całkowego jest typ integer.

Przypis:

43) dlatego wyliczenia (7.2) nie są całkami; jednakże wyliczenia mogą być promowane do int, unsigned int, long lub unsigned long, jak określono w 4.5.

Obejście:

Możesz użyć enum trick aby zainicjować tablicę wewnątrz definicji klasy.

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

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

};

Dlaczego Norma na to nie pozwala?

Bjarne wyjaśnia to trafnie proszę.:

Klasa jest zazwyczaj deklarowana w pliku nagłówkowym, a plik nagłówkowy jest zazwyczaj dołączany do wielu jednostek tłumaczeniowych. Jednak, aby uniknąć skomplikowanych reguł linkera, C++ wymaga, aby każdy obiekt miał unikalną definicję. Reguła ta zostałaby złamana, gdyby C++ pozwalało na zdefiniowanie w klasie encji, które musiały być przechowywane w pamięci jako obiektów.

Dlaczego tylko static const typy całkowe i liczby są dozwolone w inicjalizacji klasy?

Odpowiedź ukryta jest w cytacie Bjarne ' a przeczytaj ją uważnie,
"C++ wymaga, aby każdy obiekt miał unikalną definicję. Reguła ta zostałaby złamana, gdyby C++ zezwalał na definiowanie w klasie obiektów, które muszą być przechowywane w pamięci jako obiekty."

Zauważ, że tylko static const liczby całkowite mogą być traktowane jako stałe czasu kompilacji. Kompilator wie, że wartość całkowita nie zmieni się w każdej chwili, a więc może zastosować własną magię i zastosować optymalizacje, kompilator po prostu inlines takich członków klasy tj, nie są one przechowywane w pamięci już, jak potrzeba jest przechowywana w pamięci jest usunięty, daje takie zmienne wyjątek do reguły wymienione przez Bjarne.

Warto tutaj zauważyć, że nawet jeśli static const wartości całkowe mogą mieć inicjalizację w klasie, pobieranie adresów takich zmiennych nie jest dozwolone. Można przyjąć adres statycznego członek, jeśli (i tylko wtedy, gdy) ma definicję poza klasą.To dodatkowo potwierdza powyższe rozumowanie.

Enums są dozwolone, ponieważ wartości typu enumerated mogą być używane tam, gdzie oczekuje się ints.patrz cytat powyżej


Jak to się zmienia w C++11?

C++11

C++11 9.4.2 static data members
§3

Jeśli statyczny element danych jest const Typ, jego deklaracja w definicji klasy może określać brace-or-equal-initializer , w którym każda initializer-clause, która jest assignment-expression jest wyrażeniem stałym. Statyczny element danych typu literalnego może być zadeklarowany w definicji klasy za pomocą constexpr specifier; jeśli tak, jej deklaracja powinna określać nawias-lub-równy-inicjalizator , w którym każda inicjalizator-klauzula, która jest przypisanie-wyrażenie jest stałą ekspresja. [Uwaga: w obu tych przypadkach element może występować w wyrażeniach stałych. - End note ] element musi być nadal zdefiniowany w zakresie przestrzeni nazw, jeśli jest używany w programie, a definicja zakresu przestrzeni nazw nie powinna zawierać inicjatora.

Ponadto, C++11 pozwoli (§12.6.2.8) zainicjować niestatyczny element danych tam,gdzie jest zadeklarowany (w swojej klasie). Będzie to oznaczało znacznie łatwą semantykę użytkownika.

Zauważ, że te funkcje nie zostały jeszcze zaimplementowane w najnowszym gcc 4.7, więc nadal mogą pojawić się błędy kompilacji.

 152
Author: Alok Save,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-01 17:41:13

Wydaje się to reliktem z dawnych czasów prostych linkerów. Można używać zmiennych statycznych w metodach statycznych jako obejścia:

// 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();

I

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

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

I

// 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;
}

Budowa:

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

Run:

./main

Fakt, że to działa (konsekwentnie, nawet jeśli definicja klasy jest zawarta w różnych jednostkach kompilacji), pokazuje, że linker dzisiaj (gcc 4.9.2) jest wystarczająco inteligentny.

Śmieszne: nadruki 0123 na ramieniu i 3210 na x86.

 5
Author: not-a-user,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-24 10:26:49

Myślę, że ma to zapobiec mieszaniu deklaracji i definicji. (Pomyśl o problemach, które mogą wystąpić, jeśli plik zostanie umieszczony w wielu miejscach.)

 1
Author: user541686,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-11 17:19:14

To dlatego, że może być tylko jedna definicja A::a, której używają wszystkie jednostki tłumaczeniowe.

Jeśli wykonałeś static int a = 3; w klasie w nagłówku zawartym we wszystkich jednostkach tłumaczenia, otrzymasz wiele definicji. W związku z tym definicja statyczna jest wymuszona błędem kompilatora.

Za pomocą static inline lub static const można to naprawić. static inline konkretyzuje tylko symbol, jeśli jest używany w jednostce tłumaczenia i zapewnia, że łącznik wybiera i pozostawia tylko jedną kopię, jeśli jest zdefiniowany w wielu jednostkach translacyjnych ze względu na to, że znajduje się w grupie comdat. {[6] } at file scope sprawia, że kompilator nigdy nie emituje symbolu, ponieważ jest zawsze podstawiany natychmiast w kodzie, chyba że użyto extern, co nie jest dozwolone w klasie.

Należy zauważyć, że static inline int b; jest traktowana jako definicja, podczas gdy static const int b lub static const A b; są nadal traktowane jako deklaracja i muszą być zdefiniowane poza linią, jeśli nie zdefiniujesz jej wewnątrz klasy. Co ciekawe static constexpr A b; jest traktowana jako definicja, podczas gdy {[12] } jest błędem i musi mieć inicjalizator (dzieje się tak dlatego, że teraz stają się definicjami i jak każda definicja const/constexpr w zakresie pliku, wymagają inicjalizatora, którego INT nie ma, ale Typ klasy ma, ponieważ ma implicit = A() gdy jest to definicja -- clang pozwala na to, ale gcc wymaga jawnej inicjalizacji lub jest to błąd. To nie jest problem z inline zamiast). static const A b = A(); nie jest dozwolone i musi być constexpr lub inline w celu umożliwienia inicjalizator dla statycznego obiektu o typie klasy tzn. aby statyczny element typu klasy był większy niż deklaracja. Tak więc tak w pewnych sytuacjach A a; nie jest tym samym, co Jawna inicjalizacja A a = A(); (pierwsza może być deklaracją, ale jeśli tylko deklaracja jest dozwolona dla tego typu, to druga jest błędem. Ten ostatni może być użyty tylko w definicji. constexpr sprawia, że definicja). Jeśli użyjesz constexpr i podasz konstruktor domyślny, konstruktor będzie musiał być constexpr

#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;
}

Statyczny element jest deklaracją zakresu pliku extern int A::a; (która może być wykonana tylko w klasie i definicje out of line muszą odnosić się do statycznego elementu w klasie i muszą być definicjami i nie mogą zawierać extern) podczas gdy niestatyczny element jest częścią pełnej definicji typu klasy i ma te same zasady co deklaracje zakresu pliku Bez extern. Są definicjami w sposób dorozumiany. Więc int i[]; int i[5]; jest redefinicją, podczas gdy static int i[]; int A::i[5]; nie jest, ale w przeciwieństwie do 2 zewnętrznych, kompilator nadal wykryje duplikat członka, jeśli wykonasz static int i[]; static int i[5]; w klasie.

 0
Author: Lewis Kelsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-11 12:17:31

Zmienne statyczne są specyficzne dla danej klasy . Konstruktory inicjalizują atrybuty dla danej instancji.

 -3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-23 19:16:09