Definindo membros inteiros constantes estáticos na definição de classe

109

Meu entendimento é que C ++ permite que membros const estáticos sejam definidos dentro de uma classe, desde que seja um tipo inteiro.

Por que, então, o código a seguir fornece um erro de vinculador?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

O erro que recebo é:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Curiosamente, se eu comentar a chamada para std :: min, o código compila e vincula perfeitamente (embora test :: N também seja referenciado na linha anterior).

Alguma ideia do que está acontecendo?

Meu compilador é o gcc 4.4 no Linux.

HighCommander4
fonte
3
Funciona bem no Visual Studio 2010.
Filhote de cachorro
4
Este erro exato é explicado em gcc.gnu.org/wiki/…
Jonathan Wakely
No caso específico de char, você pode defini-lo como constexpr static const char &N = "n"[0];. Observe o &. Acho que isso funciona porque as strings literais são definidas automaticamente. Estou um pouco preocupado com isso - pode se comportar de forma estranha em um arquivo de cabeçalho entre diferentes unidades de tradução, já que a string provavelmente estará em vários endereços diferentes.
Aaron McDaid
1
Esta pergunta é um manifesto de como a resposta C ++ para "não use #defines para constantes" ainda é pobre.
Johannes Overmann
1
@JohannesOvermann Nesse sentido, gostaria de mencionar o uso de inline para variáveis ​​globais desde C ++ 17 inline const int N = 10, que até onde sei ainda tem um armazenamento em algum lugar definido pelo vinculador. Palavra-chave embutida também pode ser usada neste caso para fornecer definição de variável estática dentro do teste de definição de classe.
Wormer

Respostas:

72

Meu entendimento é que C ++ permite que membros const estáticos sejam definidos dentro de uma classe, desde que seja um tipo inteiro.

Você está certo. Você tem permissão para inicializar integrais const estáticos na declaração de classe, mas isso não é uma definição.

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

Curiosamente, se eu comentar a chamada para std :: min, o código compila e vincula perfeitamente (embora test :: N também seja referenciado na linha anterior).

Alguma ideia do que está acontecendo?

std :: min recebe seus parâmetros por referência const. Se os pegasse por valor, você não teria esse problema, mas como você precisa de uma referência, também precisa de uma definição.

Aqui está o capítulo / versículo:

9.4.2 / 4 - Se um staticmembro de dados for do tipo constintegral ou constenumeração, sua declaração na definição da classe pode especificar um inicializador de constante que deve obrigatoriamente ser uma expressão de constante integral (5.19). Nesse caso, o membro pode aparecer em expressões constantes integrais. O membro ainda deve ser definido em um escopo de namespace se for usado no programa e a definição de escopo de namespace não deve conter um inicializador .

Consulte a resposta de Chu para uma possível solução alternativa.

Edward Strange
fonte
Entendo, isso é interessante. Nesse caso, qual é a diferença entre fornecer o valor no ponto de declaração e fornecer o valor no ponto de definição? Qual é o recomendado?
HighCommander4
Bem, eu acredito que você pode escapar sem uma definição, contanto que você nunca realmente "use" a variável. Se você usá-lo apenas como parte de uma expressão constante, a variável nunca será usada. Caso contrário, não parece haver uma grande diferença além de ser capaz de ver o valor no cabeçalho - que pode ou não ser o que você deseja.
Edward Strange
2
A resposta concisa é estática const x = 1; é um rvalue, mas não um lvalue. O valor está disponível como uma constante em tempo de compilação (você pode dimensionar um array com ele) static const y; [sem inicializador] deve ser definido em um arquivo cpp e pode ser usado como rvalue ou lvalue.
Dale Wilson
2
Seria bom se eles pudessem estender / melhorar isso. objetos inicializados mas não definidos devem, em minha opinião, ser tratados da mesma forma que literais. Por exemplo, podemos ligar um literal 5a um const int&. Então, por que não tratar os OPs test::Ncomo o literal correspondente?
Aaron McDaid
Explicação interessante, obrigado! Isso significa que em C ++ static const ainda não substitui o inteiro #defines. enum é sempre apenas assinado int, portanto, é necessário usar classes enum para constantes individuais. Seria bastante óbvio para mim degenerar uma declaração de constante com valores constantes e conhecidos em uma constante literal, de forma que isso seria compilado sem problemas. C ++ tem um longo caminho a percorrer ...
Johannes Overmann
51

O exemplo de Bjarne Stroustrup em seu FAQ C ++ sugere que você está correto e só precisa de uma definição se você pegar o endereço.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Ele diz: "Você pode obter o endereço de um membro estático se (e somente se) ele tiver uma definição fora da classe" . O que sugere que funcionaria de outra forma. Talvez sua função min invoque endereços de alguma forma nos bastidores.

HostileFork diz não confie em SE
fonte
2
std::mintoma seus parâmetros por referência, razão pela qual uma definição é necessária.
Rakete1111 de
Como eu escreveria a definição se AE é uma classe de modelo AE <classe T> e c7 não é um int, mas T :: size_type? Eu tenho o valor inicializado como "-1" no cabeçalho, mas clang diz valor indefinido e não sei como escrever a definição.
Fabian de
@Fabian Estou viajando e falando ao telefone e um pouco ocupada ... mas acho que seu comentário parece ser melhor escrito como uma nova pergunta. Escreva um MCVE incluindo o erro que você obteve, também talvez inclua o que o gcc diz. Aposto que as pessoas diriam rapidamente o que é o quê.
HostileFork diz não confie em SE
@HostileFork: Ao escrever um MCVE, às vezes você descobre a solução sozinho. No meu caso, a resposta é template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;onde KeyContainer é um typedef de std :: vector <K>. Deve-se listar todos os parâmetros do modelo e escrever typename porque é um tipo dependente. Talvez alguém ache este comentário útil. No entanto, agora me pergunto como exportar isso em uma DLL porque a classe de modelo é, obviamente, em um cabeçalho. Preciso exportar c7 ???
Fabian de
24

Outra forma de fazer isso, para tipos inteiros, é definir constantes como enums na classe:

class test
{
public:
    enum { N = 10 };
};
Stephen Chu
fonte
2
E isso provavelmente resolveria o problema. Quando N é usado como um parâmetro para min (), ele fará com que um temporário seja criado ao invés de tentar se referir a uma variável supostamente existente.
Edward Strange
Isso tinha a vantagem de poder ser privado.
Agostino,
11

Não apenas int's. Mas você não pode definir o valor na declaração da classe. Se você tem:

class classname
{
    public:
       static int const N;
}

no arquivo .h, então você deve ter:

int const classname::N = 10;

no arquivo .cpp.

Amardeep AC9MF
fonte
2
Estou ciente de que você pode declarar uma variável de qualquer tipo dentro da declaração de classe. Eu disse que achava que constantes inteiras estáticas também poderiam ser definidas dentro da declaração de classe. Não é este o caso? Se não, por que o compilador não dá erro na linha onde tento defini-lo dentro da classe? Além disso, por que a linha std :: cout não causa um erro de vinculador, mas a linha std :: min sim?
HighCommander4
Não, não é possível definir membros estáticos na declaração de classe porque a inicialização emite código. Ao contrário de uma função embutida que também emite código, uma definição estática é globalmente exclusiva.
Amardeep AC9MF
@ HighCommander4: Você pode fornecer um inicializador para o static constmembro integral na definição da classe. Mas isso ainda não define aquele membro. Consulte a resposta de Noah Roberts para obter detalhes.
AnT
9

Esta é outra maneira de contornar o problema:

std::min(9, int(test::N));

(Acho que a resposta do Crazy Eddie descreve corretamente por que o problema existe.)

Karadoc
fonte
5
ou mesmostd::min(9, +test::N);
Tomilov Anatoliy
Mas aqui está a grande questão: tudo isso é ótimo? Eu não sei sobre vocês, mas minha grande atração em pular a definição é que isso não deve ocupar nenhuma memória e nenhuma sobrecarga ao usar o const estático.
Opux
6

A partir do C ++ 11, você pode usar:

static constexpr int N = 10;

Teoricamente, isso ainda requer que você defina a constante em um arquivo .cpp, mas, desde que você não obtenha o endereço Ndela, é muito improvável que qualquer implementação do compilador produza um erro;).

Carlo madeira
fonte
E se você precisar passar o valor como um argumento do tipo 'const int &' como no exemplo? :-)
Wormer
Isso funciona bem. Você não está instanciando N dessa forma, apenas passando uma referência const para um temporário. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood
C ++ 17 talvez, não C ++ 14, e mesmo não C ++ 17 nas versões anteriores do gcc 6.3.0 e inferiores, não é uma coisa padrão. Mas obrigado por mencionar isso.
Wormer
Ah sim, você está certo. Eu não tentei c ++ 14 no wandbox. Bem, essa é a parte onde eu disse "Isso teoricamente ainda requer que você defina a constante". Então, você está certo de que não é 'padrão'.
Carlo Wood de
3

C ++ permite que membros const estáticos sejam definidos dentro de uma classe

Não, 3.1 §2 diz:

Uma declaração é uma definição, a menos que declare uma função sem especificar o corpo da função (8.4), ela contém o especificador externo (7.1.1) ou uma especificação de ligação (7.5) e nem um inicializador nem um corpo de função, declara um dado estático membro em uma definição de classe (9.4), é uma declaração de nome de classe (9.1), é uma declaração de enum opaco (7.2) ou é uma declaração de typedef (7.1.3), uma declaração de uso (7.3. 3), ou uma diretiva de uso (7.3.4).

fredoverflow
fonte