Dlaczego nie mogę zainicjować niestałego statycznego elementu członkowskiego lub tablicy statycznej w klasie?

116

Dlaczego nie mogę zainicjować staticelementu członkowskiego lub statictablicy niebędącej stałą 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ć staticczłonków danych w klasie?
  2. Dlaczego nie mogę zainicjować statictablic w klasie, nawet consttablicy?
Yishu Fang
źródło
1
Myślę, że głównym powodem jest to, że trudno jest to naprawić. Zasadniczo mógłbyś prawdopodobnie zrobić to, o czym mówisz, ale byłyby pewne dziwne skutki uboczne. Tak jak gdyby twój przykład tablicy był dozwolony, możesz być w stanie uzyskać wartość A :: c [0], ale nie być w stanie przekazać A :: c do funkcji, ponieważ wymagałoby to adresu i czasu kompilacji stałe nie mają adresu. C ++ 11 umożliwił to częściowo dzięki użyciu constexpr.
Vaughn Cato
Świetne pytanie i gotowa odpowiedź. Link, który mi pomógł: msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

Odpowiedzi:

144

Dlaczego nie mogę zainicjować staticczłonków danych w klasie?

Standard C ++ pozwala na inicjowanie wewnątrz klasy tylko statycznych stałych typów całkowych lub wyliczeniowych. To jest powód, adla którego można inicjalizować, podczas gdy inne nie.

Odniesienie:
C ++ 03 9.4.2 Statyczne elementy członkowskie danych
§4

Jeśli statyczny element członkowski danych jest typu const integra lub const wyliczenia, jego deklaracja w definicji klasy może określać inicjator stałej, który powinien być wyrażeniem stałym integralnym (5.19). W takim przypadku element członkowski może pojawić się w całkowitych wyrażeniach stałych. Element członkowski powinien być nadal zdefiniowany w zakresie przestrzeni nazw, jeśli jest używany w programie, a definicja zakresu przestrzeni nazw nie powinna zawierać inicjatora.

Jakie są typy całkowite?

C ++ 03 3.9.1 Typy podstawowe
§7

Typy bool, char, wchar_t oraz typy całkowite ze znakiem i bez znaku są zbiorczo nazywane typami całkowitymi.43) Synonimem typu całkowitego jest typ całkowity.

Notatka:

43) Dlatego wyliczenia (7.2) nie są integralne; jednak wyliczenia mogą być promowane do int, unsigned int, long lub unsigned long, jak określono w 4.5.

Obejście:

Możesz użyć sztuczki wyliczeniowej, 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 zezwala?

Bjarne wyjaśnia to trafnie tutaj :

Klasa jest zwykle deklarowana w pliku nagłówkowym, a plik nagłówkowy jest zwykle dołączany do wielu jednostek tłumaczeniowych. Jednak aby uniknąć skomplikowanych reguł konsolidatora, C ++ wymaga, aby każdy obiekt miał unikalną definicję. Ta reguła zostałaby złamana, gdyby C ++ pozwolił na definiowanie w klasie jednostek, które musiałyby być przechowywane w pamięci jako obiekty.

Dlaczego dozwolone są tylko static consttypy całkowite i wyliczenia In-class In-class?

Odpowiedź jest ukryta w cytacie Bjarne'a, przeczytaj go uważnie:
„C ++ wymaga, aby każdy obiekt miał unikalną definicję. Ta reguła zostałaby złamana, gdyby C ++ pozwolił na definiowanie w klasie encji, które musiały 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 żadnym momencie i dlatego może zastosować własną magię i zastosować optymalizacje, kompilator po prostu wstawia takie elementy klasy, tj. Nie są już przechowywane w pamięci, ponieważ potrzeba przechowywania w pamięci jest usunięta , daje takim zmiennym wyjątek od reguły wspomnianej przez Bjarne'a.

Warto zauważyć, że nawet jeśli static constwartości całkowite mogą mieć inicjalizację w klasie, przyjmowanie adresu takich zmiennych jest niedozwolone. Można wziąć adres statycznego elementu członkowskiego, jeśli (i tylko wtedy) ma on definicję spoza klasy. To dodatkowo potwierdza powyższe rozumowanie.

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


Jak to się zmienia w C ++ 11?

C ++ 11 łagodzi to ograniczenie do pewnego stopnia.

C ++ 11 9.4.2 Statyczne składowe danych
§3

Jeśli statyczny element członkowski danych ma typ literału const, jego deklaracja w definicji klasy może określać inicjator nawiasu klamrowego lub równego, w którym każda klauzula inicjatora, która jest wyrażeniem przypisania, jest wyrażeniem stałym. Statyczny element członkowski danych typu literał może być zadeklarowany w definicji klasy z wartością, constexpr specifier;jeśli tak, jego deklaracja powinna określać inicjator nawiasu klamrowego lub równego, w którym każda klauzula inicjalizatora, która jest wyrażeniem przypisaniawyrażeniem stałym. [Uwaga: w obu tych przypadkach element członkowski może pojawić się w stałych wyrażeniach. —Nuta końcowa] Element członkowski powinien nadal być zdefiniowany w zakresie przestrzeni nazw, jeśli jest używany w programie, a definicja zakresu przestrzeni nazw nie powinna zawierać inicjatora.

Ponadto, C ++ 11 będzie pozwalają (§12.6.2.8) non-static członek dane mają być zainicjowana w którym jest zadeklarowana (w swojej klasie). Będzie to oznaczać dużo łatwą semantykę użytkownika.

Zauważ, że te funkcje nie zostały jeszcze zaimplementowane w najnowszej wersji gcc 4.7, więc nadal możesz otrzymywać błędy kompilacji.

Alok Save
źródło
7
W C ++ 11 jest inaczej. Odpowiedź może wymagać aktualizacji.
bames53
4
To nie wydaje się być prawdą: „Zauważ, że tylko statyczne liczby całkowite const mogą być traktowane jako stałe czasu kompilacji. Kompilator wie, że wartość całkowita nie zmieni się w żadnym momencie i dlatego może zastosować swoją własną magię i zastosować optymalizacje, kompilator po prostu w liniach takich członków klasy, tj. nie są już przechowywane w pamięci , „ Czy na pewno niekoniecznie są przechowywane w pamięci? A jeśli podam definicje członkom? Co by &memberzwróciło?
Nawaz
2
@Als: Tak. Oto moje pytanie. Dlaczego więc C ++ zezwala na inicjalizację w klasie tylko dla typów całkowitych, nie odpowiada poprawnie twoja odpowiedź. Zastanów się, dlaczego nie pozwala na inicjalizację static const char*członka?
Nawaz
3
@Nawaz: Ponieważ C ++ 03 zezwalał tylko na inicjalizację stałej dla statycznej i stałej integralnej oraz typu wyliczeniowego const i żadnego innego typu, C ++ 11 rozszerza to do typu literału const, który rozluźnia normy dla inicjalizacji w klasie. w C ++ 03 było być może przeoczenie, które uzasadniało zmianę i dlatego zostało poprawione w C ++ 11, jeśli istnieją jakieś tradycyjne taktyczne powody zmiany, nie jestem ich świadomy. im.
Alok Save
4
Wspomniany par „Obejście problemu” nie działa z g ++.
iammilind
4

Wydaje się, że jest to relikt z dawnych czasów prostych linkerów. Możesz użyć 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

biegać:

./main

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

Zabawne: wydruki 0123na ramieniu i 3210na x86.

nie jest użytkownikiem
źródło
1

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

user541686
źródło
0

Dzieje się tak, ponieważ może istnieć tylko jedna definicja tego A::a, której używają wszystkie jednostki tłumaczeniowe.

Jeśli występowałeś static int a = 3;w klasie w nagłówku zawartym we wszystkich jednostkach tłumaczeniowych, otrzymasz wiele definicji. Dlatego definicja statyczna, która nie jest poza linią, jest wymuszona jako błąd kompilatora.

Korzystanie static inlinelub static constśrodki zaradcze. static inlinekonkretyzuje symbol tylko wtedy, gdy jest używany w jednostce tłumaczeniowej i zapewnia, że ​​konsolidator wybiera i pozostawia tylko jedną kopię, jeśli jest zdefiniowana w wielu jednostkach tłumaczeniowych, ponieważ znajduje się w grupie comdat. constat zakres powoduje, że kompilator nigdy nie emituje symbolu, ponieważ jest on zawsze podstawiany natychmiast w kodzie, chyba że externjest używany, co jest niedozwolone w klasie.

Jedną rzeczą, na którą należy zwrócić uwagę, jest zezwolenie na inicjalizację obiektu statycznego z typem klasy, tj. Uczynienie statycznego elementu członkowskiego typu klasy czymś więcej niż deklaracją. Zatem tak w pewnych sytuacjach nie jest tym samym, co jawna inicjalizacja (ta pierwsza może być deklaracją, ale jeśli tylko deklaracja jest dozwolona dla tego typu, to druga jest błędem.Tej drugiej można użyć tylko w definicji.static inline int b; jest traktowana jako definicja, podczas gdy static const int blub static const A b;nadal są traktowane jako deklaracja i muszą być zdefiniowane poza linią, jeśli nie zdefiniujesz jej wewnątrz klasy. Co ciekawe, static constexpr A b;jest traktowany jako definicja, podczas gdy static constexpr int b;jest błędem i musi mieć inicjalizator (dzieje się tak, ponieważ teraz stają się definicjami i jak każda definicja const / constexpr w zakresie pliku, wymagają inicjalizatora, którego int nie ma, ale typ klasy robi, ponieważ zawiera ukrytą = A()definicję - clang na to pozwala, ale gcc wymaga jawnej inicjalizacji lub jest to błąd. Nie jest to problem z inline zamiast tego). static const A b = A();nie jest dozwolone i musi być constexprlubinlineA a;A a = A();constexpr Czyni ją definicją ). Jeśli używasz constexprkonstruktora domyślnego i określasz go, to 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 członkowski jest jawną deklaracją zakresu pliku extern int A::a;(która może być wykonana tylko w klasie, a definicje poza wierszem muszą odwoływać się do statycznego elementu członkowskiego w klasie i muszą być definicjami i nie mogą zawierać extern), podczas gdy element niestatyczny jest częścią pełną definicję typu klasy i mają takie same zasady, jak w przypadku deklaracji zakresu plików bez extern. Są to definicje w sposób dorozumiany. Więc int i[]; int i[5];jest to redefinicja, podczas gdy static int i[]; int A::i[5];nie jest, ale w przeciwieństwie do 2 externs, kompilator nadal wykryje zduplikowany element członkowski, jeśli zrobisz to static int i[]; static int i[5];w klasie.

Lewis Kelsey
źródło
-3

zmienne statyczne są specyficzne dla klasy. Konstruktorzy inicjują atrybuty ESPECIALY dla instancji.


źródło