Undefinierter Verweis auf statisches Klassenmitglied

201

Kann jemand erklären, warum der folgende Code nicht kompiliert wird? Zumindest unter g ++ 4.2.4.

Und interessanter, warum wird es kompiliert, wenn ich MEMBER auf int umsetze?

#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
quelle
Ich habe die Frage so bearbeitet, dass der Code um vier Leerzeichen eingerückt wird, anstatt <pre> <code> </ code> </ pre> zu verwenden. Dies bedeutet, dass die spitzen Klammern nicht als HTML interpretiert werden.
Steve Jessop
stackoverflow.com/questions/16284629/… Sie können sich auf diese Frage beziehen.
Tooba Iqbal

Antworten:

199

Sie müssen das statische Element tatsächlich irgendwo definieren (nach der Klassendefinition). Versuche dies:

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

const int Foo::MEMBER;

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

Das sollte die undefinierte Referenz loswerden.

Drew Hall
quelle
3
Guter Punkt, die Inline-Initialisierung der statischen Konstantenzahl erstellt eine Ganzzahlkonstante mit Gültigkeitsbereich, deren Adresse Sie nicht verwenden können, und der Vektor verwendet einen Referenzparameter.
Evan Teran
10
Diese Antwort behandelt nur den ersten Teil der Frage. Der zweite Teil ist viel interessanter: Warum funktioniert das Hinzufügen einer NOP-Besetzung, ohne dass eine externe Deklaration erforderlich ist?
Nobar
32
Ich habe nur ein bisschen Zeit damit verbracht herauszufinden, dass die Zuordnung der statischen Variablen in der Implementierungsdatei und nicht im Header erfolgen sollte, wenn sich die Klassendefinition in einer Header-Datei befindet.
shanet
1
@shanet: Sehr guter Punkt - das hätte ich in meiner Antwort erwähnen sollen!
Drew Hall
Aber wenn ich es als const deklariere, kann ich den Wert dieser Variablen nicht ändern?
Namratha
78

Das Problem ergibt sich aus einem interessanten Zusammenprall neuer C ++ - Funktionen und dem, was Sie versuchen zu tun. Schauen wir uns zunächst die push_backSignatur an:

void push_back(const T&)

Es wird ein Verweis auf ein Objekt vom Typ erwartet T. Nach dem alten Initialisierungssystem existiert ein solches Mitglied. Der folgende Code wird beispielsweise problemlos kompiliert:

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

Dies liegt daran, dass sich irgendwo ein tatsächliches Objekt befindet, in dem dieser Wert gespeichert ist. Wenn Sie jedoch zu der neuen Methode zum Festlegen statischer const-Elemente wechseln, wie oben beschrieben, Foo::MEMBERhandelt es sich nicht mehr um ein Objekt. Es ist eine Konstante, ähnlich wie:

#define MEMBER 1

Aber ohne die Kopfschmerzen eines Präprozessor-Makros (und mit Typensicherheit). Das bedeutet, dass der Vektor, der eine Referenz erwartet, keine bekommen kann.

Douglas Mayle
quelle
2
danke, das hat geholfen ... das könnte sich für stackoverflow.com/questions/1995113/strangest-language-feature qualifizieren, wenn es nicht schon da ist ...
Andre Holzner
1
Erwähnenswert ist auch, dass MSVC die nicht besetzte Version ohne Beschwerden akzeptiert.
porges
4
-1: Das ist einfach nicht wahr. Sie sollten immer noch statische Elemente definieren, die inline initialisiert wurden, wenn sie irgendwo verwendet werden. Dass Compiler-Optimierungen Ihren Linker-Fehler möglicherweise beseitigen, ändert daran nichts. In diesem Fall erfolgt Ihre Umwandlung von lWert in rWert (dank der (int)Besetzung) in der Übersetzungseinheit mit perfekter Sichtbarkeit der Konstante und Foo::MEMBERwird nicht mehr verwendet . Dies steht im Gegensatz zum ersten Funktionsaufruf, bei dem eine Referenz herumgereicht und an anderer Stelle ausgewertet wird.
Leichtigkeitsrennen im Orbit
Was ist mit void push_back( const T& value );? const&'s können mit rWerten binden.
Kostas
59

Der C ++ - Standard erfordert eine Definition für Ihr statisches const-Mitglied, wenn die Definition irgendwie benötigt wird.

Die Definition ist beispielsweise erforderlich, wenn die Adresse verwendet wird. push_backNimmt seinen Parameter als const-Referenz und so benötigt der Compiler unbedingt die Adresse Ihres Mitglieds und Sie müssen sie im Namespace definieren.

Wenn Sie die Konstante explizit umwandeln, erstellen Sie eine temporäre Konstante, und diese temporäre Konstante ist an die Referenz gebunden (gemäß speziellen Regeln im Standard).

Dies ist ein wirklich interessanter Fall, und ich denke tatsächlich, dass es sich lohnt, ein Problem anzusprechen, damit der Standard geändert wird, um das gleiche Verhalten für Ihr ständiges Mitglied zu haben!

Obwohl dies auf seltsame Weise als legitime Verwendung des unären '+' - Operators angesehen werden könnte. Grundsätzlich ist das Ergebnis von unary +ein r-Wert, daher gelten die Regeln für die Bindung von r-Werten an const-Referenzen, und wir verwenden nicht die Adresse unseres statischen const-Elements:

v.push_back( +Foo::MEMBER );
Richard Corden
quelle
3
+1. Ja, es ist sicherlich seltsam, dass für ein Objekt x vom Typ T der Ausdruck "(T) x" verwendet werden kann, um eine Konstante zu binden, während "x" dies nicht kann. Ich liebe deine Beobachtung über "unary +"! Wer hätte gedacht, dass das arme kleine "unäre +" tatsächlich einen Nutzen hat ... :)
j_random_hacker
3
Denken Sie an den allgemeinen Fall ... Gibt es einen anderen Objekttyp in C ++, der die Eigenschaft hat, dass er (1) nur dann als l-Wert verwendet werden kann, wenn er definiert wurde, aber (2) ohne Wert in einen r-Wert konvertiert werden kann definiert?
j_random_hacker
Gute Frage, und zumindest im Moment kann ich mir keine anderen Beispiele vorstellen. Dies ist wahrscheinlich nur hier der Fall, weil das Komitee die vorhandene Syntax meist nur wiederverwendete.
Richard Corden
@RichardCorden: Wie hat das Unary + das gelöst?
Blood-HaZaRd
1
@ Blood-HaZaRd: Bevor rvalue referenziert, war die einzige Überladung von push_backa const &. Die direkte Verwendung des Mitglieds führte dazu, dass das Mitglied an die Referenz gebunden war, für die eine Adresse erforderlich war. Durch Hinzufügen von +wird jedoch eine temporäre Datei mit dem Wert des Mitglieds erstellt. Die Referenz wird dann an diese temporäre Adresse gebunden, anstatt dass das Mitglied eine Adresse haben muss.
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
quelle
1

Keine Ahnung, warum die Besetzung funktioniert, aber Foo :: MEMBER wird erst beim ersten Laden von Foo zugewiesen. Da Sie es nie laden, wird es nie zugewiesen. Wenn Sie irgendwo einen Verweis auf ein Foo hätten, würde es wahrscheinlich funktionieren.

Paul Tomblin
quelle
Ich denke, Sie beantworten Ihre eigene Frage: Die Besetzung funktioniert, weil sie (eine vorübergehende) Referenz erstellt.
Jaap Versteegh
1

Mit C ++ 11 wäre das Obige für Basistypen wie möglich

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

Der constexprTeil erstellt einen statischen Ausdruck im Gegensatz zu einer statischen Variablen - und das verhält sich wie eine extrem einfache Inline-Methodendefinition. Der Ansatz erwies sich jedoch mit C-String-Kontexten innerhalb von Template-Klassen als etwas wackelig.

Starturtle
quelle
Dies stellte sich für mich als wesentlich heraus, da die "statische Konstante int MEMBER = 1;" wird benötigt, um MEMBER in Switches zu verwenden, während die externe Deklaration benötigt wird, um es in Vektoren zu verwenden, und Sie können nicht beide gleichzeitig haben. Aber der Ausdruck, den Sie hier geben, funktioniert für beide, zumindest mit meinem Compiler.
Ben Farmer
0

In Bezug auf die zweite Frage: push_ref verwendet die Referenz als Parameter, und Sie können keine Referenz auf das statische Konstantenmitglied einer Klasse / Struktur haben. Sobald Sie static_cast aufrufen, wird eine temporäre Variable erstellt. Und ein Verweis auf dieses Objekt kann übergeben werden, alles funktioniert einwandfrei.

Oder zumindest mein Kollege, der das gelöst hat, hat es gesagt.

Quarra
quelle