2010-11-17 27 views
10

J'ai récemment découvert un problème ennuyeux dans un grand programme que je développe; Je voudrais comprendre comment le réparer de la meilleure façon. J'ai réduit le code à l'exemple minimal suivant.Constantes intégrales C++ + opérateur de choix = problème!

#include <iostream> 
using std::cin; 
using std::cout; 

class MagicNumbers 
{ 
public: 
    static const int BIG = 100; 
    static const int SMALL = 10; 
}; 

int main() 
{ 
    int choice; 
    cout << "How much stuff do you want?\n"; 
    cin >> choice; 
    int stuff = (choice < 20) ? MagicNumbers::SMALL : MagicNumbers::BIG; // PROBLEM! 
    cout << "You got " << stuff << "\n"; 
    return 0; 
} 

Je reçois des erreurs de lien dans gcc 4.1.2 lors de la compilation avec -O0 ou -O1 mais tout est OK lors de la compilation avec -O2 ou -O3. Il lie bien en utilisant MS Visual Studio 2005 sans tenir compte des options d'optimisation.

test.cpp:(.text+0xab): undefined reference to `MagicNumbers::SMALL'

test.cpp:(.text+0xb3): undefined reference to `MagicNumbers::BIG'

I regardé le code d'assemblage intermédiaire, et oui, le code non optimisé considéré petit et grand int en tant que variables externes, tandis que l'optimisation utilise une des nombres réels. Chacune des modifications suivantes résout le problème:

  • utilisation enum au lieu d'int pour les constantes: enum {SMALL = 10}

  • Cast la constante (quelconque) à chaque usage: (int)MagicNumbers::SMALL ou (int)MagicNumbers::BIG ou même MagicNumbers::SMALL + 0

  • Utiliser une macro: #define SMALL 10

  • Ne pas utiliser l'opérateur de choix: if (choice < 20) stuff = MagicNumbers::SMALL; else stuff = MagicNumbers::BIG;

J'aime la première option meilleure (cependant, ce n'est pas idéal parce que nous utilisons en fait uint32_t au lieu de int pour ces constantes et ENUM est synonyme int). Mais ce que je veux vraiment demander est: à qui le bug est-il? Est-ce que je suis responsable de ce que je ne comprends pas comment fonctionnent les constantes intégrales statiques? Dois-je accuser gcc et espérer un correctif (ou peut-être que la dernière version a déjà un correctif, ou peut-être y a-t-il un argument de ligne de commande obscur pour que cela fonctionne)?

Pendant ce temps, je viens de compiler mon code avec des optimisations, et il est une douleur de débogage: -O3

Répondre

7

Malgré les conseils classiques, j'ai constaté que static const int ... me donne invariablement plus de maux de tête que le bon vieux enum { BIG = 100, SMALL = 10 };. Et avec C++ 11 fournissant des énumérations fortement typées, j'ai maintenant encore moins de raisons d'utiliser static const int ....

+0

Enums fortement typés? Cool! – dreamlax

+0

Son encore "C++ 0x" .. BS dit que le "x" est hexadécimal. :) –

+0

Allons-nous prendre des paris sur cela? ;-) –

1

Je serais mal à affirmer que c'est le bug de tout le monde.

Les intégrales statiques constantes au point de déclaration ne sont pas des variables, ce sont des expressions constantes. Pour qu'il y ait une variable, vous devez toujours la définir.

Les règles de l'opérateur ternaire sont assez absurdement complexes, probablement nécessairement, et ne disent rien sur les expressions constantes, seulement les valeurs rv; De toute évidence, le compilateur pense qu'ils devraient être variables à moins que l'optimisation ne soit trop poussée. Je pense qu'il est libre d'interpréter l'expression de toute façon (comme une expression constante ou variable).

7

membres de données statiques don't work like that en C++:

Static data members are not part of objects of a given class type; they are separate objects. As a result, the declaration of a static data member is not considered a definition. The data member is declared in class scope, but definition is performed at file scope. These static members have external linkage.

Vous êtes seulement ces constantes déclarent, même si vous les cours d'initialisation.Il vous reste à définir les à périmètre d'espace de noms:

class MagicNumbers 
{ 
public: 
    static const int BIG = 100; 
    static const int SMALL = 10; 
}; 

const int MagicNumbers::BIG; 
const int MagicNumbers::SMALL; 

qui va se débarrasser des erreurs de lien.

+3

Je * pense * que c'est faux. Recherche ... –

+0

@John, j'ai effectivement testé cela, vous savez;) –

+0

@John, quelle partie pensez-vous est faux? Ce n'est pas comme ça que j'irais résoudre le problème, 'scope' est un peu inapproprié, et ça ne dit pas vraiment POURQUOI l'utilisation de ternary ne fonctionne pas mais si/else le fait ... mais il a raison. –

1

Je suis nouveau à C++, mais je pense que votre déclaration de classe déclare seulement que les membres statiques existent, vous devez toujours les définir quelque part:

class MagicNumbers 
{ 
public: 
    static const int BIG; 
    static const int SMALL; 
}; 

const int MagicNumbers::BIG = 100; 
const int MagicNumbers::SMALL = 10; 

Les niveaux d'optimisation plus élevés incluent probablement un niveau de analyse statique suffisamment approfondie pour déterminer que BIG et SMALL peuvent être échangés avec leurs valeurs réelles et ne pas leur donner de stockage réel (la sémantique sera la même), de sorte que la définition de ces variables dans ce cas serait redondant, d'où les liens OK.

0

Vous devez encore allouer de l'espace pour eux quelque part:

class MagicNumbers 
{ 
public: 
    static const int BIG = 100; 
    static const int SMALL = 10; 
}; 
const int MagicNumbers::BIG; 
const int MagicNumbers::SMALL; 
3

Heh, selon la norme C++, 9.4.2 (class.static.data):

If a static data member is of const literal type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. —end note ] The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.

Ainsi la déclaration est correct, mais vous devez toujours avoir une définition quelque part. J'ai toujours pensé que vous pouviez maîtriser la définition, mais je suppose que ce n'est pas conforme à la norme.

+0

il est normal de ne pas les définir ... parfois, le terme 'used' est défini dans la norme pour une signification très particulière. Voir la réponse de litb pour la référence à la partie standard pertinente et le rapport de défaut concernant cette utilisation particulière. –

+0

@Matthieu, doux! J'adore stackoverflow si seulement parce que litb en sait plus sur le C++ que moi. – MSN

+0

le gars est vraiment impressionnant, je suis moi-même fan de ses templates "hack" :) –

20

This is a known issue. La norme est à blâmer ou vous pour ne pas fournir une définition de la statique. Selon votre point de vue :)

+0

+1 merci pour la référence. Je suppose que je devrais lire les rapports de bugs. :) –

+0

Cela aurait dû être la réponse choisie: \ –

+0

@Noah: Cela dépend si vous voulez comprendre le problème ou le réparer. FWIW, j'ai voté cette réponse. –

0

Pourquoi vos nombres magiques sont-ils dans une classe?

namespace MagicNumbers { 
    const int BIG = 100; 
    const int SMALL = 10; 
} 

Problème résolu sans avoir à se soucier des failles de la norme C++.