Perché non posso inizializzare un membro statico non const o un array statico nella classe?


116

Perché non posso inizializzare un staticmembro o un staticarray non const in una classe?

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

il compilatore emette i seguenti errori:

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

Ho due domande:

  1. Perché non posso inizializzare statici membri dei dati in classe?
  2. Perché non posso inizializzare gli staticarray in classe, nemmeno l' constarray?

1
Penso che il motivo principale sia che è difficile ottenere il giusto. In linea di principio, potresti probabilmente fare quello di cui parli, ma ci sarebbero alcuni strani effetti collaterali. Come se il tuo esempio di array fosse consentito, potresti essere in grado di ottenere il valore di A :: c [0], ma non essere in grado di passare A :: c a una funzione poiché ciò richiederebbe un indirizzo e in fase di compilazione le costanti non hanno un indirizzo. C ++ 11 ha abilitato alcune di queste cose tramite l'uso di constexpr.
Vaughn Cato

Ottima domanda e risposta makred. Link che mi ha aiutato: msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

Risposte:


144

Perché non riesco a inizializzare statici membri dei dati in classe?

Lo standard C ++ consente di inizializzare solo i tipi di integrale costante statico o di enumerazione all'interno della classe. Questo è il motivo per cui aè consentito inizializzare mentre altri no.

Riferimento:
C ++ 03 9.4.2 Membri dati statici
§4

Se un membro di dati statici è di tipo const integral o const enumeration, la sua dichiarazione nella definizione di classe può specificare un inizializzatore di costante che deve essere un'espressione di costante integrale (5.19). In tal caso, il membro può apparire in espressioni di costanti integrali. Il membro deve ancora essere definito in un ambito dello spazio dei nomi se viene utilizzato nel programma e la definizione dell'ambito dello spazio dei nomi non deve contenere un inizializzatore.

Cosa sono i tipi integrali?

C ++ 03 3.9.1 Tipi fondamentali
§7

I tipi bool, char, wchar_t e i tipi interi con segno e senza segno sono chiamati collettivamente tipi integrali.43) Un sinonimo di tipo integrale è tipo intero.

Nota:

43) Pertanto, le enumerazioni (7.2) non sono integrali; tuttavia, le enumerazioni possono essere promosse a int, unsigned int, long o unsigned long, come specificato in 4.5.

Soluzione:

È possibile utilizzare il trucco enum per inizializzare un array all'interno della definizione della classe.

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

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

};

Perché lo Standard non lo consente?

Bjarne lo spiega appropriatamente qui :

Una classe è tipicamente dichiarata in un file di intestazione e un file di intestazione è tipicamente incluso in molte unità di traduzione. Tuttavia, per evitare complicate regole del linker, C ++ richiede che ogni oggetto abbia una definizione univoca. Questa regola sarebbe infranta se C ++ consentisse la definizione in classe di entità che dovevano essere archiviate in memoria come oggetti.

Perché sono static constconsentiti solo tipi integrali ed enumerazioni in classe?

La risposta è nascosta nella citazione di Bjarne, letta attentamente,
"C ++ richiede che ogni oggetto abbia una definizione univoca. Quella regola sarebbe infranta se C ++ consentisse la definizione in classe di entità che dovevano essere archiviate in memoria come oggetti."

Notare che solo gli static constinteri possono essere trattati come costanti del tempo di compilazione. Il compilatore sa che il valore intero non cambierà in qualsiasi momento e quindipuò applicare la sua magia e applicare ottimizzazioni, il compilatore semplicemente integra tali membri della classe cioè non sono più archiviati in memoria, poiché viene rimossa la necessità di essere archiviati in memoria , fornisce a tali variabili l'eccezione alla regola menzionata da Bjarne.

È interessante notare che anche se i static constvalori integrali possono avere l'inizializzazione in classe, non è consentito prendere l'indirizzo di tali variabili. Si può prendere l'indirizzo di un membro statico se (e solo se) ha una definizione fuori classe. Ciò convalida ulteriormente il ragionamento sopra.

Gli enum sono consentiti perché i valori di un tipo enumerato possono essere utilizzati laddove sono previsti int. vedere la citazione sopra


Come cambia questo in C ++ 11?

C ++ 11 allenta la restrizione in una certa misura.

C ++ 11 9.4.2 Membri dati statici
§3

Se un membro dati statico è di tipo letterale const, la sua dichiarazione nella definizione della classe può specificare un inizializzatore di parentesi graffa o uguale in cui ogni clausola di inizializzazione che è un'espressione di assegnazione è un'espressione costante. Un membro di dati statici di tipo letterale può essere dichiarato nella definizione della classe con il constexpr specifier;se è così, la sua dichiarazione deve specificare un inizializzatore di parentesi graffa o uguale in cui ogni clausola di inizializzazione che è un'espressione di assegnazioneè un'espressione costante. [Nota: in entrambi i casi, il membro può apparire in espressioni costanti. —End note] Il membro deve ancora essere definito in un ambito dello spazio dei nomi se viene utilizzato nel programma e la definizione dell'ambito dello spazio dei nomi non deve contenere un inizializzatore.

Inoltre, C ++ 11 vi permetterà (§12.6.2.8) un componente di dati non statico da inizializzare in cui è dichiarata (nella sua classe). Ciò significherà una semantica utente molto semplice.

Nota che queste funzionalità non sono state ancora implementate nell'ultimo gcc 4.7, quindi potresti ancora ricevere errori di compilazione.


7
Le cose sono diverse in c ++ 11. La risposta potrebbe utilizzare l'aggiornamento.
bames53

4
Questo non sembra essere vero: "Nota che solo gli interi const statici possono essere trattati come costanti del tempo di compilazione. Il compilatore sa che il valore intero non cambierà in qualsiasi momento e quindi può applicare la sua magia e applicare ottimizzazioni, il compilatore semplicemente inline tali membri della classe cioè, non sono più archiviati in memoria , " Sei sicuro che non siano necessariamente archiviati in memoria? Cosa succede se fornisco definizioni per i membri? Cosa sarebbe &membertornato?
Nawaz

2
@Als: Sì. Questa è la mia domanda. Quindi, perché C ++ consente l'inizializzazione in classe solo per i tipi integrali, la risposta non risponde correttamente. Pensa al motivo per cui non consente l'inizializzazione per il static const char*membro?
Nawaz

3
@Nawaz: poiché C ++ 03 consentiva solo l' inizializzatore costante per il tipo di enumerazione statico e const integrale e const e nessun altro tipo, C ++ 11 lo estende a un tipo letterale const che allenta le norme per l'inizializzazione in classe. in C ++ 03 era forse una svista che giustificava una modifica e quindi è stata corretta in C ++ 11, se ci sono ragioni tattiche tradizionali per il cambiamento non ne sono a conoscenza.Se ne sei a conoscenza sentiti libero di condividere loro.
Alok Save

4
Il parametro "Workaround" che hai menzionato non funziona con g ++.
iammilind

4

Questo sembra un relitto dei vecchi tempi dei semplici linker. È possibile utilizzare variabili statiche nei metodi statici come soluzione alternativa:

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

e

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

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

e

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

costruire:

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

correre:

./main

Il fatto che funzioni (coerentemente, anche se la definizione della classe è inclusa in diverse unità di compilazione), mostra che il linker oggi (gcc 4.9.2) è in realtà abbastanza intelligente.

Divertente: stampa 0123sul braccio e 3210su x86.


1

Penso che sia per impedirti di mescolare dichiarazioni e definizioni. (Pensa ai problemi che potrebbero verificarsi se includi il file in più posizioni.)


0

È perché può esserci una sola definizione di A::a quella utilizzata da tutte le unità di traduzione.

Se ti esibisci static int a = 3;in una classe in un'intestazione inclusa in tutte le unità di traduzione, otterrai più definizioni. Pertanto, la definizione non fuori linea di una statica viene forzatamente resa un errore del compilatore.

Usando static inlineo static constrimedi a questo. static inlineconcretizza il simbolo solo se è usato nell'unità di traduzione e garantisce che il linker selezioni e lasci solo una copia se è definito in più unità di traduzione poiché si trova in un gruppo comdat. constnell'ambito del file fa in modo che il compilatore non emetta mai un simbolo perché viene sempre sostituito immediatamente nel codice a meno che non externvenga utilizzato, il che non è consentito in una classe.

Una cosa da notare è che static inline int b;viene trattata come una definizione mentre static const int bo static const A b;è ancora trattata come una dichiarazione e deve essere definita fuori linea se non la si definisce all'interno della classe. È interessante notare che static constexpr A b;viene trattato come una definizione, mentre static constexpr int b;è un errore e deve avere un inizializzatore (questo perché ora diventano definizioni e come ogni definizione const / constexpr nell'ambito del file, richiedono un inizializzatore che un int non ha ma un tipo di classe fa perché ha un implicito = A()quando si tratta di una definizione - clang lo consente ma gcc richiede l'inizializzazione esplicita o è un errore. Questo non è un problema con inline invece). static const A b = A();non è consentito e deve essere constexproinlineal fine di consentire un inizializzatore per un oggetto statico con tipo di classe, cioè per rendere un membro statico di tipo classe più che una dichiarazione. Quindi sì in certe situazioni A a;non è la stessa cosa che inizializzare esplicitamente A a = A();(la prima può essere una dichiarazione ma se è consentita solo una dichiarazione per quel tipo, la seconda è un errore. La seconda può essere usata solo su una definizione. constexprRende una definizione ). Se si utilizza constexpre si specifica un costruttore predefinito, il costruttore dovrà essereconstexpr

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

Un membro statico è una dichiarazione di ambito file definitivo extern int A::a;(che può essere fatta solo nella classe e le definizioni fuori linea devono fare riferimento a un membro statico in una classe e devono essere definizioni e non possono contenere extern) mentre un membro non statico fa parte di la definizione di tipo completa di una classe e hanno le stesse regole delle dichiarazioni di ambito di file senza extern. Sono implicitamente definizioni. Quindi int i[]; int i[5];è una ridefinizione mentre static int i[]; int A::i[5];non lo è ma a differenza di 2 extern, il compilatore rileverà comunque un membro duplicato se lo fai static int i[]; static int i[5];nella classe.


-3

le variabili statiche sono specifiche di una classe. I costruttori inizializzano gli attributi ESPECIALY per un'istanza.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.