Riferimento indefinito al membro di classe statico


201

Qualcuno può spiegare perché il seguente codice non verrà compilato? Almeno su g ++ 4.2.4.

E più interessante, perché verrà compilato quando lancio MEMBER su 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;
}

Ho modificato la domanda per indentare il codice di quattro spazi anziché utilizzare <pre> <code> </code> </pre>. Ciò significa che le parentesi angolari non sono interpretate come HTML.
Steve Jessop,

stackoverflow.com/questions/16284629/… Puoi fare riferimento a questa domanda.
Tooba Iqbal,

Risposte:


199

È necessario definire effettivamente il membro statico da qualche parte (dopo la definizione della classe). Prova questo:

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

const int Foo::MEMBER;

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

Ciò dovrebbe eliminare il riferimento indefinito.


3
Un buon punto, l'inizializzazione di numeri interi statici incorporati crea una costante intera con ambito di cui non si può prendere l'indirizzo e il vettore accetta un parametro di riferimento.
Evan Teran,

10
Questa risposta riguarda solo la prima parte della domanda. La seconda parte è molto più interessante: perché l'aggiunta di un cast NOP lo fa funzionare senza richiedere la dichiarazione esterna?
nobar

32
Ho appena trascorso un bel po 'di tempo a capire che se la definizione della classe è in un file di intestazione, l'allocazione della variabile statica dovrebbe essere nel file di implementazione, non nell'intestazione.
Shanet,

1
@shanet: ottimo punto - avrei dovuto menzionarlo nella mia risposta!
Ha disegnato la sala il

Ma se lo dichiaro come const, non è possibile per me cambiare il valore di quella variabile?
Namratha,

78

Il problema sorge a causa di un interessante scontro di nuove funzionalità C ++ e di ciò che stai cercando di fare. Innanzitutto, diamo un'occhiata alla push_backfirma:

void push_back(const T&)

Si aspetta un riferimento a un oggetto di tipo T. Sotto il vecchio sistema di inizializzazione, esiste un membro del genere. Ad esempio, il codice seguente viene compilato correttamente:

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

Questo perché c'è un oggetto reale da qualche parte in cui è memorizzato quel valore. Se, tuttavia, si passa al nuovo metodo per specificare i membri const statici, come sopra, Foo::MEMBERnon è più un oggetto. È una costante, in qualche modo simile a:

#define MEMBER 1

Ma senza il mal di testa di una macro di preprocessore (e con sicurezza di tipo). Ciò significa che il vettore, che si aspetta un riferimento, non può ottenerne uno.


2
grazie, questo mi ha aiutato ... che potrebbe qualificarsi per stackoverflow.com/questions/1995113/strangest-language-feature se non fosse già lì ...
Andre Holzner,

1
Vale anche la pena notare che MSVC accetta la versione non cast senza lamentele.
porges,

4
-1: Questo semplicemente non è vero. Dovresti ancora definire membri statici inizializzati in linea, quando vengono usati in qualche luogo. Che le ottimizzazioni del compilatore possano eliminare l'errore del linker non lo cambiano. In questo caso la conversione da lvalue a rvalue (grazie al (int)cast) si verifica nell'unità di traduzione con una perfetta visibilità della costante e Foo::MEMBERnon viene più utilizzata . Ciò è in contrasto con la prima chiamata di funzione, in cui un riferimento viene passato e valutato altrove.
Corse di leggerezza in orbita,

Che dire void push_back( const T& value );? const&può legarsi con i valori.
Kostas,

59

Lo standard C ++ richiede una definizione per il membro const statico se la definizione è in qualche modo necessaria.

La definizione è obbligatoria, ad esempio se viene utilizzato l'indirizzo. push_backaccetta il suo parametro come riferimento const, quindi il compilatore necessita rigorosamente dell'indirizzo del proprio membro ed è necessario definirlo nello spazio dei nomi.

Quando si lancia esplicitamente la costante, si crea un temporaneo ed è questo temporaneo che è associato al riferimento (in base a regole speciali nello standard).

Questo è un caso davvero interessante, e in realtà penso che valga la pena sollevare un problema in modo che lo std venga cambiato per avere lo stesso comportamento per il tuo membro costante!

Sebbene, in un modo strano, questo potrebbe essere visto come un uso legittimo dell'operatore unario "+". Fondamentalmente il risultato di unary +è un valore e quindi si applicano le regole per l'associazione dei valori ai riferimenti const e non usiamo l'indirizzo del nostro membro const statico:

v.push_back( +Foo::MEMBER );

3
+1. Sì, è certamente strano che per un oggetto x di tipo T, l'espressione "(T) x" possa essere usata per associare un riferimento const mentre la semplice "x" non può. Adoro la tua osservazione su "unario +"! Chi avrebbe mai pensato che il povero "unario +" avesse effettivamente avuto un uso ... :)
j_random_hacker

3
Pensando al caso generale ... Esiste un altro tipo di oggetto in C ++ che ha la proprietà che (1) può essere usato come un valore solo se è stato definito ma (2) può essere convertito in un valore senza essere definito?
j_random_hacker,

Bella domanda, e almeno al momento non riesco a pensare ad altri esempi. Questo è probabilmente solo qui perché il comitato stava principalmente riutilizzando la sintassi esistente.
Richard Corden,

@RichardCorden: come lo risolve l'Unario +?
Blood-HaZaRd

1
@ Blood-HaZaRd: prima dei riferimenti al valore l'unico sovraccarico di push_backera a const &. L'uso diretto del membro ha comportato che il membro fosse associato al riferimento, il che richiedeva che avesse un indirizzo. Tuttavia, l'aggiunta di +crea un temporaneo con il valore del membro. Il riferimento quindi si lega a quel temporaneo piuttosto che richiedere al membro di avere un indirizzo.
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;

1

Non ho idea del perché il cast funzioni, ma Foo :: MEMBER non viene assegnato fino alla prima volta che Foo viene caricato, e poiché non lo carichi mai, non viene mai assegnato. Se avessi un riferimento a un Foo da qualche parte, probabilmente funzionerebbe.


Penso che stai rispondendo alla tua domanda: il cast funziona perché crea un riferimento (temporaneo).
Jaap Versteegh,

1

Con C ++ 11, quanto sopra sarebbe possibile per i tipi di base come

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

La constexprparte crea un'espressione statica in contrapposizione a una variabile statica , che si comporta proprio come una definizione del metodo inline estremamente semplice. Tuttavia, l'approccio si è rivelato un po 'traballante con i constexprs di stringhe C all'interno delle classi dei template.


Questo si è rivelato essenziale per me, perché "static const int MEMBER = 1;" è necessario per utilizzare MEMBER negli switch, mentre la dichiarazione esterna è necessaria per utilizzarlo nei vettori e non è possibile avere entrambi contemporaneamente. Ma l'espressione si dà qui fa il lavoro per entrambi, almeno con il mio compilatore.
Ben Farmer,

0

Per quanto riguarda la seconda domanda: push_ref accetta il riferimento come parametro e non è possibile avere un riferimento a un membro const statico di una classe / struttura. Dopo aver chiamato static_cast, viene creata una variabile temporanea. E un riferimento a questo oggetto può essere passato, tutto funziona bene.

O almeno il mio collega che ha risolto questo ha detto così.

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.