2008-11-07 8 views
172

Quelqu'un peut-il expliquer pourquoi le code suivant ne sera pas compilé? Au moins sur g ++ 4.2.4.Référence indéfinie à un membre de classe statique

Et plus intéressant, pourquoi compilera-t-il quand je jette MEMBER à 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; 
} 
+0

J'ai modifié la question pour mettre le code en retrait de quatre espaces au lieu d'utiliser

. Cela signifie que les chevrons ne sont pas interprétés comme HTML. –

+0

acclamations :) La règle min de 10 caractères est parfois ennuyante;) –

Répondre

176

Vous devez définir le membre statique quelque part (après la définition de la classe). Essayez ceci:

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

const int Foo::MEMBER; 

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

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

+3

Bon point, l'initialisation en ligne const const statique crée une constante de portée dont vous ne pouvez pas prendre l'adresse, et le vecteur prend un paramètre de référence. –

+9

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'un cast NOP le fait-il fonctionner sans exiger la déclaration externe? – nobar

+27

Je viens de passer un bon moment à 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

69

Le problème vient d'un conflit intéressant de nouvelles fonctionnalités C++ et de ce que vous essayez de faire. Tout d'abord, nous allons jeter un oeil à la signature push_back:

void push_back(const T&) 

Il attend un référence à un objet de type T. Sous l'ancien système d'initialisation, un tel membre existe. Par exemple, le code suivant 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 est un objet réel qui a quelque part que la valeur stockée. Si, toutefois, vous passez à la nouvelle méthode de spécification des membres const statiques, comme vous l'avez ci-dessus, Foo::MEMBER n'est plus un objet. Il est une constante, un peu semblable à:

#define MEMBER 1 

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

+1

Cela fait sens –

+1

merci, cela a aidé ... qui pourrait se qualifier pour http://stackoverflow.com/questions/1995113/strangest-language-feature si ce n'est pas déjà là ... –

+1

Aussi à noter que MSVC accepte la version non-cast sans plaintes. – porges

1

Aucune idée du fonctionnement de la conversion, mais Foo :: MEMBER n'est pas alloué jusqu'à ce que Foo soit chargé pour la première fois, 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.

+0

Je pense que vous répondez à votre propre question: La distribution fonctionne parce qu'elle crée une référence (temporaire). –

56

La norme C++ requiert une définition pour votre membre const statique si la définition est nécessaire.

La définition est obligatoire, par exemple si l'adresse est utilisée. push_back prend 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 avez explicitement cast la constante, vous créez un temporaire et c'est ce temporaire qui est lié à la référence (sous des règles spéciales dans la norme).

Ceci est un cas très intéressant, et je pense réellement 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, de façon étrange, cela puisse ê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 pour la fixation des rvalues ​​à Const références applicables et nous ne pas utiliser l'adresse de notre membre statique const:

v.push_back(+Foo::MEMBER); 
+2

+1. Oui c'est certainement bizarre que pour un objet x de type T, l'expression "(T) x" puisse être utilisée pour lier une const ref alors que "x" ne le peut pas. J'aime ton observation sur "unaire +"! Qui aurait pensé que ce pauvre petit "unaire +" avait réellement un usage ... :) –

+3

En pensant au cas général ... Y at-il un autre type d'objet en C++ qui a la propriété qu'il (1) peut être utilisé comme lvalue seulement si elle a été définie mais (2) peut être convertie en valeur sans être définie? –

+0

Bonne question, et au moins pour le moment je ne peux pas penser à d'autres exemples. Ce n'est probablement que parce que le comité réutilisait simplement la syntaxe existante. –

9

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; 
+0

+1 'Une réponse courte' – dariush

0

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

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

1

Avec C++ 11, ci-dessus serait possible pour les types de base comme

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

La partie constexpr crée une expression statique par opposition à une variables statique - et qui se comporte comme un définition de méthode en ligne extrêmement simple. L'approche s'est avérée un peu bancale avec C-string constexprs à l'intérieur des classes de modèles, cependant.

Questions connexes