Referência indefinida ao membro da classe estática

201

Alguém pode explicar por que o código a seguir não será compilado? Pelo menos em g ++ 4.2.4.

E mais interessante, por que ele será compilado quando eu converter MEMBER para int?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}
Pawel Piatkowski
fonte
Editei a pergunta para recuar o código em quatro espaços, em vez de usar <pre> <code> </code> </pre>. Isso significa que os colchetes angulares não são interpretados como HTML.
187 Steve Jessop #
stackoverflow.com/questions/16284629/… Você pode consultar esta pergunta.
Tooba Iqbal

Respostas:

199

Você realmente precisa definir o membro estático em algum lugar (após a definição da classe). Tente o seguinte:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

Isso deve se livrar da referência indefinida.

Drew Hall
fonte
3
Bom ponto, a inicialização de um número inteiro estático estático inline cria uma constante inteira com escopo que você não pode obter o endereço e o vetor usa um parâmetro de referência.
Evan Teran
10
Esta resposta aborda apenas a primeira parte da pergunta. A segunda parte é muito mais interessante: Por que adicionar um elenco NOP faz com que funcione sem exigir a declaração externa?
Nobar
32
Passei um bom tempo descobrindo que, se a definição de classe estiver em um arquivo de cabeçalho, a alocação da variável estática deve estar no arquivo de implementação, não no cabeçalho.
Shanet
1
@ Shanet: Muito bom ponto - eu deveria ter mencionado isso na minha resposta!
Tirou Salão
Mas se eu declarar como const, não é possível alterar o valor dessa variável?
Namratha
78

O problema ocorre devido a um conflito interessante de novos recursos do C ++ e o que você está tentando fazer. Primeiro, vamos dar uma olhada na push_backassinatura:

void push_back(const T&)

Está esperando uma referência a um objeto do tipo T. Sob o antigo sistema de inicialização, esse membro existe. Por exemplo, o código a seguir compila perfeitamente:

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

Isso ocorre porque existe um objeto real em algum lugar que possui esse valor armazenado. Se, no entanto, você mudar para o novo método de especificar membros const estáticos, como você fez acima, Foo::MEMBERnão será mais um objeto. É uma constante, um pouco semelhante a:

#define MEMBER 1

Mas sem as dores de cabeça de uma macro de pré-processador (e com segurança de tipo). Isso significa que o vetor, que está esperando uma referência, não pode obter um.

Douglas Mayle
fonte
2
graças, que ajudou ... que poderiam se qualificar para stackoverflow.com/questions/1995113/strangest-language-feature se não é já ...
Andre Holzner
1
Também é importante notar que o MSVC aceita a versão não convertida sem reclamações.
PORGES
4
-1: isso simplesmente não é verdade. Você ainda deve definir membros estáticos inicializados em linha, quando eles são usados ​​em algum lugar. Que otimizações do compilador podem se livrar do erro do vinculador não muda isso. Nesse caso, sua conversão lvalue em rvalue (graças à conversão (int)) ocorre na unidade de tradução com visibilidade perfeita da constante e Foo::MEMBERnão é mais usada por odr . Isso contrasta com a primeira chamada de função, onde uma referência é passada e avaliada em outro lugar.
Corridas do Lightness em órbita
Que tal void push_back( const T& value );? const&pode ligar com rvalues.
Kostas
59

O padrão C ++ requer uma definição para seu membro const estático, se a definição for necessária de alguma forma.

A definição é necessária, por exemplo, se o endereço for usado. push_backtoma seu parâmetro por referência const e, portanto, estritamente o compilador precisa do endereço do seu membro e você precisa defini-lo no espaço para nome.

Quando você lança explicitamente a constante, cria um temporário e é esse temporário que é vinculado à referência (sob regras especiais no padrão).

Este é um caso realmente interessante e, na verdade, acho que vale a pena levantar uma questão para que o std seja alterado para ter o mesmo comportamento do seu membro constante!

Embora, de uma maneira estranha, isso possa ser visto como um uso legítimo do operador '+' unário. Basicamente, o resultado de unary +é um rvalue e, portanto, as regras para vincular rvalues ​​a referências const se aplicam e não usamos o endereço do nosso membro const estático:

v.push_back( +Foo::MEMBER );
Richard Corden
fonte
3
+1. Sim, certamente é estranho que, para um objeto x do tipo T, a expressão "(T) x" possa ser usada para vincular uma const ref enquanto a planície "x" não possa. Adoro a sua observação sobre "unário +"! Quem teria pensado que aquele pobre "unário +", na verdade, teve um uso ... :)
j_random_hacker
3
Pensando no caso geral ... Existe algum outro tipo de objeto em C ++ que possua a propriedade de que (1) possa ser usado como um valor l apenas se tiver sido definido, mas (2) puder ser convertido em um valor sem ser definiram?
j_random_hacker
Boa pergunta, e pelo menos no momento não consigo pensar em outros exemplos. Provavelmente, isso é apenas aqui porque o comitê estava apenas reutilizando a sintaxe existente.
Richard Corden 29/05
@ RichardCorden: como o unário + resolveu isso?
Blood-Hazard
1
@ Blood-HaZaRd: Antes do rvalue referenciar, a única sobrecarga de push_backera a const &. O uso do membro resultou diretamente no membro sendo vinculado à referência, o que exigia que ele tivesse um endereço. No entanto, adicionar o +cria um temporário com o valor do membro. A referência então se liga àquele temporário em vez de exigir que o membro tenha um endereço.
Richard Corden
10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;
iso9660
fonte
1

Não faço ideia por que o elenco funciona, mas Foo :: MEMBER não é alocado até a primeira vez que o Foo é carregado, e como você nunca o carrega, nunca é alocado. Se você tivesse uma referência a um Foo em algum lugar, provavelmente funcionaria.

Paul Tomblin
fonte
Acho que você está respondendo sua própria pergunta: o elenco funciona porque cria uma referência (temporária).
Jaap Versteegh
1

Com o C ++ 11, o acima seria possível para tipos básicos como

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

A constexprpeça cria uma expressão estática em oposição a uma variável estática - e se comporta exatamente como uma definição de método embutido extremamente simples. A abordagem se mostrou um pouco instável com constexprs de string C dentro de classes de modelo.

starturtle
fonte
Isso acabou sendo essencial para mim, porque a "static const int MEMBER = 1;" é necessário para usar MEMBER em comutadores, enquanto a declaração externa é necessária para usá-lo em vetores e você não pode ter os dois ao mesmo tempo. Mas a expressão que você dá aqui faz trabalho para ambos, pelo menos com o meu compilador.
Ben Farmer
0

Em relação à segunda pergunta: push_ref usa a referência como parâmetro e você não pode ter uma referência ao mem const constante de uma classe / estrutura. Depois de chamar static_cast, uma variável temporária é criada. E uma referência a esse objeto pode ser passada, tudo funciona bem.

Ou pelo menos meu colega que resolveu isso disse isso.

Quarra
fonte