Dlaczego nie mogę zainicjować static
elementu członkowskiego lub static
tablicy 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:
- Dlaczego nie mogę zainicjować
static
członków danych w klasie? - Dlaczego nie mogę zainicjować
static
tablic w klasie, nawetconst
tablicy?
Odpowiedzi:
Dlaczego nie mogę zainicjować
static
czł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,
a
dla którego można inicjalizować, podczas gdy inne nie.Odniesienie:
C ++ 03 9.4.2 Statyczne elementy członkowskie danych
§4
Jakie są typy całkowite?
C ++ 03 3.9.1 Typy podstawowe
§7
Notatka:
Obejście:
Możesz użyć sztuczki wyliczeniowej, aby zainicjować tablicę wewnątrz definicji klasy.
Dlaczego norma na to nie zezwala?
Bjarne wyjaśnia to trafnie tutaj :
Dlaczego dozwolone są tylko
static const
typy 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 const
wartoś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
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.
źródło
&member
zwróciło?static const char*
członka?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:
i
i
budować:
biegać:
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
0123
na ramieniu i3210
na x86.źródło
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).
źródło
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 inline
lubstatic const
środki zaradcze.static inline
konkretyzuje 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.const
at zakres powoduje, że kompilator nigdy nie emituje symbolu, ponieważ jest on zawsze podstawiany natychmiast w kodzie, chyba żeextern
jest 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 gdystatic const int b
lubstatic 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 gdystatic 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ćconstexpr
lubinline
A a;
A a = A();
constexpr
Czyni ją definicją ). Jeśli używaszconstexpr
konstruktora domyślnego i określasz go, to konstruktor będzie musiał byćconstexpr
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 bezextern
. Są to definicje w sposób dorozumiany. Więcint i[]; int i[5];
jest to redefinicja, podczas gdystatic 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 tostatic int i[]; static int i[5];
w klasie.źródło
zmienne statyczne są specyficzne dla klasy. Konstruktorzy inicjują atrybuty ESPECIALY dla instancji.
źródło