2010-11-17 18 views
10

Recentemente ho scoperto un fastidioso problema in qualche grande programma che sto sviluppando; mi piacerebbe capire come risolverlo nel modo migliore. Ho ridotto il codice al seguente esempio minimo.Costanti integrali C++ + operatore di scelta = problema!

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

ottengo errori di collegamento a gcc 4.1.2 quando si compila con -O0 o -O1 ma tutto è ok quando si compila con -O2 o -O3. Si collega bene con MS Visual Studio 2005 indipendentemente dalle opzioni di ottimizzazione.

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

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

Ho guardato il codice complesso intermedio, e sì, il codice non ottimizzato considerate piccole e grandi come variabili int esterne, mentre l'ottimizzato quello utilizzato i numeri reali. Ciascuna delle seguenti modifiche risolve il problema: ad ogni utilizzo enum {SMALL = 10}

  • Cast costante (uno)::

    • Uso enumerazione invece di int per le costanti (int)MagicNumbers::SMALL o (int)MagicNumbers::BIG o anche MagicNumbers::SMALL + 0

    • utilizzare una macro: #define SMALL 10

    • non si utilizza l'operatore di scelta: if (choice < 20) stuff = MagicNumbers::SMALL; else stuff = MagicNumbers::BIG;

    mi piace la prima opzione migliore (tuttavia, non è l'ideale perché abbiamo effettivamente utilizzare uint32_t invece di int per queste costanti, ed enum è sinonimo di int). Ma quello che voglio veramente chiedere è: di chi è il bug?

    Sono io il colpevole per non capire come funzionano le costanti integrali statiche?

    Devo incolpare gcc e sperare in una correzione (o forse l'ultima versione ha già una correzione, o forse c'è un oscuro argomento da riga di comando per fare in modo che funzioni)?

    Nel frattempo, ho solo compilare il mio codice con le ottimizzazioni, ed è un dolore per il debug: -O3

  • risposta

    7

    Nonostante il consiglio convenzionale, ho trovato che static const int ... mi dà sempre più mal di testa del buon vecchio enum { BIG = 100, SMALL = 10 };. E con C++ 11 che fornisce enumerazioni fortemente tipizzate, ora ho ancora meno motivi per usare static const int ....

    +0

    Enum fortemente tipizzati? Freddo! – dreamlax

    +0

    E 'ancora "C++ 0x" .. BS dice che "x" è esadecimale. :) –

    +0

    Dobbiamo prendere le scommesse su questo? ;-) –

    1

    sarei difficile riuscire ad affermare che si tratta di bug di nessuno.

    Gli integrali statici costanti dati nel punto di dichiarazione non sono variabili, sono espressioni costanti. Perché ci sia una variabile devi ancora definirla.

    Le regole sull'operatore ternario sono abbastanza assurdamente complesse, probabilmente necessariamente così, e in realtà non dice nulla sulle espressioni costanti, solo valori limite; ovviamente il compilatore pensa che dovrebbero essere variabili a meno che l'ottimizzazione non sia aumentata. Penso che sia libero di interpretare l'espressione in entrambi i modi (come espressione costante o variabile).

    7

    membri dati statici don't work like that in 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.

    sei solo dichiarando queste costanti, anche se li stai inizializzazione.Hai ancora a definire loro a portata namespace:

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

    che sbarazzarsi degli errori di collegamento.

    +3

    I * penso * questo è sbagliato. Ricercando ... –

    +0

    @ John, in realtà ho provato questo, lo sai;) –

    +0

    @ John, quale parte pensi sia sbagliata? Non è come andrei a risolvere il problema, 'file scope' è un po 'approssimativo, e in realtà non dice PERCHÉ l'uso del ternario non funziona, ma se lo fa/else ... ma è corretto. –

    1

    Sono nuovo di C++, ma penso che la dichiarazione di classe dichiara soltanto che esistono i membri statici, è ancora necessario definire da qualche parte:

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

    I livelli di ottimizzazione più alti probabilmente includono un livello di analisi statica abbastanza approfondita da determinare che BIG e SMALL possono essere scambiati con i loro valori effettivi e non dare loro alcuna memoria effettiva (la semantica sarà la stessa), quindi definire queste variabili in questa circostanza sarebbe ridondante, quindi collega OK.

    0

    è ancora necessario per allocare spazio per loro da qualche parte:

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

    Eh, secondo lo standard 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.

    Quindi la dichiarazione è corretto, ma è ancora necessario avere una definizione da qualche parte. Ho sempre pensato che si potesse abilitare la definizione, ma suppongo che non sia conforme allo standard.

    +0

    è conforme alla norma non definirli ... a volte il termine "usato" è definito nello standard per un significato molto specifico. Vedere la risposta di litb per il riferimento alla parte standard pertinente e il rapporto sui difetti relativi a questo particolare uso. –

    +0

    @Matthieu, dolce! Adoro lo stackoverflow se solo perché litb ne sa più di C++. – MSN

    +0

    il ragazzo è davvero impressionante, io stesso sono un fan dei suoi modelli "hack" :) –

    20

    This is a known issue. Lo standard è da biasimare o tu per non aver fornito una definizione della statica. A seconda del tuo punto di vista :)

    +0

    +1 grazie per il riferimento. Credo che dovrei leggere le segnalazioni di bug. :) –

    +0

    Questa dovrebbe essere la risposta scelta: \ –

    +0

    @Noah: Dipende se vuoi capire il problema o correggerlo. FWIW, ho votato questa risposta. –

    0

    Perché i tuoi numeri magici in una classe?

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

    Problema risolto senza problemi di errori nello standard C++.