Por que não consigo inicializar um static
membro não const ou static
matriz em uma 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;
}
o compilador emite os seguintes erros:
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]’
Eu tenho duas perguntas:
- Por que não consigo inicializar
static
membros de dados na classe? - Por que não consigo inicializar
static
matrizes na classe, mesmo aconst
matriz?
Respostas:
Por que não consigo inicializar
static
membros de dados na classe?O padrão C ++ permite que apenas integral constante estática ou tipos de enumeração sejam inicializados dentro da classe. Este é o motivo pelo qual
a
pode ser inicializado, enquanto outros não.Referência:
C ++ 03 9.4.2 Membros de dados estáticos
§4
O que são tipos integrais?
C ++ 03 3.9.1 Tipos fundamentais
§7
Nota de rodapé:
Gambiarra:
Você poderia usar o truque enum para inicializar um array dentro da definição de sua classe.
Por que o padrão não permite isso?
Bjarne explica isso apropriadamente aqui :
Por que apenas
static const
tipos integrais e enums são permitidos na inicialização em classe?A resposta está oculta na citação de Bjarne, lida com atenção:
"C ++ requer que cada objeto tenha uma definição única. Essa regra seria quebrada se C ++ permitisse a definição em classe de entidades que precisavam ser armazenadas na memória como objetos."
Observe que apenas
static const
inteiros podem ser tratados como constantes de tempo de compilação. O compilador sabe que o valor inteiro não mudará a qualquer momento e, portanto, pode aplicar sua própria magia e aplicar otimizações, o compilador simplesmente inline esses membros da classe, ou seja, eles não são mais armazenados na memória, pois a necessidade de serem armazenados na memória é removida , dá a tais variáveis a exceção à regra mencionada por Bjarne.É importante notar aqui que mesmo que os
static const
valores integrais possam ter inicialização em classe, tomar o endereço de tais variáveis não é permitido. Pode-se obter o endereço de um membro estático se (e somente se) ele tiver uma definição fora da classe. Isso valida ainda mais o raciocínio acima.enums são permitidos porque valores de um tipo enumerado podem ser usados onde ints são esperados. veja a citação acima
Como isso muda no C ++ 11?
C ++ 11 relaxa a restrição até certo ponto.
C ++ 11 9.4.2 Membros de dados estáticos
§3
Além disso, C ++ 11 vai permitir (§12.6.2.8) um membro de dados não-estático para ser inicializado em que é declarado (na sua classe). Isso significa uma semântica do usuário muito fácil.
Observe que esses recursos ainda não foram implementados no último gcc 4.7, então você ainda pode obter erros de compilação.
fonte
&member
voltaria?static const char*
membro?Isso parece uma relíquia dos velhos tempos dos linkers simples. Você pode usar variáveis estáticas em métodos estáticos como solução alternativa:
e
e
Construir:
corre:
O fato de que isso funciona (consistentemente, mesmo se a definição da classe estiver incluída em unidades de compilação diferentes), mostra que o vinculador hoje (gcc 4.9.2) é realmente inteligente o suficiente.
Engraçado: impressões
0123
no braço e3210
no x86.fonte
Acho que é para evitar que você misture declarações e definições. (Pense nos problemas que podem ocorrer se você incluir o arquivo em vários lugares.)
fonte
É porque só pode haver uma definição de
A::a
que todas as unidades de tradução usam.Se você executou
static int a = 3;
em uma classe em um cabeçalho incluído em todas as unidades de tradução, você obterá várias definições. Portanto, a definição não fora de linha de uma estática é forçosamente transformada em um erro do compilador.Usando
static inline
oustatic const
corrige isso.static inline
só concretiza o símbolo se for usado na unidade de tradução e garante que o vinculador só selecione e deixe uma cópia se estiver definido em várias unidades de tradução por estar em um grupo comdat.const
no escopo do arquivo faz com que o compilador nunca emita um símbolo porque ele sempre é substituído imediatamente no código, a menos queextern
seja usado, o que não é permitido em uma classe.Uma coisa a observar é que
static inline int b;
é tratado como uma definição, enquantostatic const int b
oustatic const A b;
ainda é tratado como uma declaração e deve ser definido fora da linha se você não defini-lo dentro da classe. Curiosamente,static constexpr A b;
é tratado como uma definição, enquantostatic constexpr int b;
é um erro e deve ter um inicializador (isso porque agora se tornam definições e como qualquer definição const / constexpr no escopo do arquivo, eles requerem um inicializador que um int não tem, mas um tipo de classe faz porque tem um implícito= A()
quando é uma definição - o clang permite isso, mas o gcc requer que você inicialize explicitamente ou é um erro. Em vez disso, não é um problema com inline).static const A b = A();
não é permitido e deve serconstexpr
ouinline
a fim de permitir um inicializador para um objeto estático com tipo de classe, isto é, fazer um membro estático do tipo de classe mais do que uma declaração. Portanto, sim, em certas situações,A a;
não é o mesmo que inicializar explicitamenteA a = A();
(o primeiro pode ser uma declaração, mas se apenas uma declaração for permitida para esse tipo, o último é um erro. O último só pode ser usado em uma definição.constexpr
Torna-o uma definição ) Se você usarconstexpr
e especificar um construtor padrão, então o construtor precisará serconstexpr
Um membro estático é uma declaração de escopo de arquivo definitiva
extern int A::a;
(que só pode ser feita na classe e as definições fora de linha devem se referir a um membro estático em uma classe e devem ser definições e não podem conter extern) enquanto um membro não estático faz parte de a definição de tipo completa de uma classe e têm as mesmas regras que as declarações de escopo de arquivo semextern
. Eles são definições implicitamente. Portanto,int i[]; int i[5];
é uma redefinição, ao passo questatic int i[]; int A::i[5];
não é, mas ao contrário de 2 externos, o compilador ainda detectará um membro duplicado se você fizer issostatic int i[]; static int i[5];
na classe.fonte
variáveis estáticas são específicas de uma classe. Construtores inicializam atributos ESPECIALY para uma instância.
fonte