Référence non définie au membre de classe statique

201

Quelqu'un peut-il expliquer pourquoi le code suivant ne se compile pas? Au moins sur g ++ 4.2.4.

Et plus intéressant, pourquoi cela se compilera-t-il lorsque je lancerai MEMBER sur 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
la source
J'ai modifié la question pour indenter le code de quatre espaces au lieu d'utiliser <pre> <code> </code> </pre>. Cela signifie que les crochets angulaires ne sont pas interprétés comme du HTML.
Steve Jessop
stackoverflow.com/questions/16284629/… Vous pouvez vous référer à cette question.
Tooba Iqbal

Réponses:

199

Vous devez réellement définir le membre statique quelque part (après la définition de classe). Essaye ça:

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

const int Foo::MEMBER;

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

Cela devrait se débarrasser de la référence non définie.

Drew Hall
la source
3
Bon point, l'initialisation d'entier de constante statique en ligne crée une constante entière de portée dont vous ne pouvez pas prendre l'adresse, et le vecteur prend un paramètre de référence.
Evan Teran
10
Cette réponse ne concerne que la première partie de la question. La deuxième partie est beaucoup plus intéressante: pourquoi l'ajout d'une distribution NOP fait-il fonctionner sans nécessiter la déclaration externe?
nobar
32
Je viens de passer un bon bout de temps à comprendre que si la définition de classe est dans un fichier d'en-tête, alors l'allocation de la variable statique devrait être dans le fichier d'implémentation, pas dans l'en-tête.
shanet
1
@shanet: Très bon point - j'aurais dû le mentionner dans ma réponse!
Drew Hall
Mais si je le déclare const, ne puis-je pas modifier la valeur de cette variable?
Namratha
78

Le problème vient d'un conflit intéressant de nouvelles fonctionnalités C ++ et de ce que vous essayez de faire. Jetons d'abord un coup d'œil à la push_backsignature:

void push_back(const T&)

Il attend une référence à un objet de type T. Sous l'ancien système d'initialisation, un tel membre existe. Par exemple, le code suivant se compile très bien:

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

C'est parce qu'il y a un objet réel quelque part qui a cette valeur stockée dedans. Si, cependant, vous passez à la nouvelle méthode de spécification des membres const stat, comme vous l'avez ci-dessus, Foo::MEMBERn'est plus un objet. C'est une constante, un peu semblable à:

#define MEMBER 1

Mais sans les maux de tête d'une macro de préprocesseur (et avec la sécurité de type). Cela signifie que le vecteur, qui attend une référence, ne peut pas en obtenir une.

Douglas Mayle
la source
2
merci, cela a aidé ... qui pourrait se qualifier pour stackoverflow.com/questions/1995113/strangest-language-feature s'il n'est pas déjà là ...
Andre Holzner
1
Il convient également de noter que MSVC accepte la version non-cast sans plaintes.
porges
4
-1: Ce n'est tout simplement pas vrai. Vous êtes toujours censé définir les membres statiques initialisés en ligne, lorsqu'ils sont utilisés quelque part. Que les optimisations du compilateur puissent se débarrasser de votre erreur de l'éditeur de liens ne change rien à cela. Dans ce cas, votre conversion lvalue-to-rvalue (grâce à la (int)distribution) se produit dans l'unité de traduction avec une visibilité parfaite de la constante, et le Foo::MEMBERn'est plus utilisé . Cela contraste avec le premier appel de fonction, où une référence est transmise et évaluée ailleurs.
Courses de légèreté en orbite
Et alors void push_back( const T& value );? const&peut se lier avec rvalues.
Kostas
59

La norme C ++ nécessite une définition pour votre membre const statique si la définition est en quelque sorte nécessaire.

La définition est obligatoire, par exemple si son adresse est utilisée. push_backprend son paramètre par référence const, et donc strictement le compilateur a besoin de l'adresse de votre membre et vous devez le définir dans l'espace de noms.

Lorsque vous transtypez explicitement la constante, vous créez un temporaire et c'est ce temporaire qui est lié à la référence (selon des règles spéciales dans la norme).

C'est un cas vraiment intéressant, et je pense en fait qu'il vaut la peine de soulever un problème afin que le std soit changé pour avoir le même comportement pour votre membre constant!

Bien que, d'une manière étrange, cela pourrait être considéré comme une utilisation légitime de l'opérateur unaire «+». Fondamentalement, le résultat de la unary +est une rvalue et donc les règles de liaison des rvalues ​​aux références const s'appliquent et nous n'utilisons pas l'adresse de notre membre const statique:

v.push_back( +Foo::MEMBER );
Richard Corden
la source
3
+1. Oui, il est certainement étrange que pour un objet x de type T, l'expression "(T) x" puisse être utilisée pour lier une const const tandis que le simple "x" ne peut pas. J'adore ton observation sur "unaire +"! Qui aurait pensé que le pauvre petit "unaire +" avait en fait une utilité ... :)
j_random_hacker
3
En pensant au cas général ... Y a-t-il un autre type d'objet en C ++ qui a la propriété qu'il (1) ne peut être utilisé comme valeur l que s'il a été défini mais (2) peut être converti en valeur r sans être défini?
j_random_hacker
Bonne question, et au moins pour le moment je ne peux penser à aucun autre exemple. C'est probablement seulement ici parce que le comité réutilisait principalement la syntaxe existante.
Richard Corden
@RichardCorden: comment les unaires + ont-ils résolu cela?
Blood-HaZaRd
1
@ Blood-HaZaRd: Avant les références rvalue, la seule surcharge de push_backétait a const &. L'utilisation directe du membre a eu pour conséquence que le membre était lié à la référence, ce qui exigeait qu'il ait une adresse. Cependant, l'ajout de +crée un temporaire avec la valeur du membre. La référence se lie alors à celle temporaire plutôt que d'exiger que le membre ait une adresse.
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
la source
1

Je ne sais pas pourquoi le casting fonctionne, mais Foo :: MEMBER n'est pas alloué avant le premier chargement de Foo, et puisque vous ne le chargez jamais, il n'est jamais alloué. Si vous aviez une référence à un Foo quelque part, cela fonctionnerait probablement.

Paul Tomblin
la source
Je pense que vous répondez à votre propre question: le casting fonctionne parce qu'il crée une référence (temporaire).
Jaap Versteegh
1

Avec C ++ 11, ce qui précède serait possible pour les types de base comme

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

La constexprpartie crée une expression statique par opposition à une variable statique - et qui se comporte exactement comme une définition de méthode en ligne extrêmement simple. Cependant, l'approche s'est avérée un peu bancale avec les constexprs de chaîne C dans les classes de modèle.

effrayé
la source
Cela s'est avéré essentiel pour moi, car le "static const int MEMBER = 1;" est nécessaire pour utiliser MEMBER dans les commutateurs, tandis que la déclaration externe est nécessaire pour l'utiliser dans les vecteurs, et vous ne pouvez pas avoir les deux en même temps. Mais l'expression que vous donnez ici fait le travail pour les deux, au moins avec mon compilateur.
Ben Farmer
0

En ce qui concerne la deuxième question: push_ref prend la référence comme paramètre, et vous ne pouvez pas avoir de référence à memeber const statique d'une classe / structure. Une fois que vous appelez static_cast, une variable temporaire est créée. Et une référence à cet objet peut être passée, tout fonctionne très bien.

Ou du moins mon collègue qui a résolu cela l'a dit.

Quarra
la source