2012-08-11 15 views
8

Ho una breve domanda sull'articolo 48 in "Effective C++" di Scott Meyers. io proprio non capire il codice copiato dal libro di seguito,Modello in C++, perché usare enum

#include <iostream> 
    using namespace std; 

    template <unsigned n> 
    struct Factorial 
    { 
     enum { value=n*Factorial<n-1>::value }; 
    }; 

    template <> 
    struct Factorial<0> 
    { 
     enum { value=1}; 
    }; 

    int main() 
    { 
     cout<<Factorial<5>::value<<endl; 
     cout<<Factorial<10>::value<<endl; 
    } 

Perché devo utilizzare enum nella programmazione modello? Esiste un modo alternativo per farlo? Grazie per l'aiuto in anticipo.

risposta

8

Si potrebbe utilizzare anche static const int:

template <unsigned n> 
struct Factorial 
{ 
    static const int value= n * Factorial<n-1>::value; 
}; 

template <> 
struct Factorial<0> 
{ 
    static const int value= 1; 
}; 

Questo dovrebbe andare bene anche. Il risultato è lo stesso in entrambi i casi.

Oppure si potrebbe usare modello di classe esistente, come std::integral_constant (in C++ 11 solo) come:

template <unsigned n> 
struct Factorial : std::integral_constant<int,n * Factorial<n-1>::value> {}; 

template <> 
struct Factorial<0> : std::integral_constant<int,1> {}; 
+0

Questo in realtà non risponde alla domanda, motivo per cui ha usato "enum". – Puppy

+0

@Nawaz a quanto pare conosce la risposta, ma non lo dice chiaramente. In alcuni compilatori, 'static const int' è ** non garantito ** una costante in fase di compilazione, perché lo standard pre-C++ 11 non richiede al compilatore di fare un tentativo esaustivo per risolverlo. Quindi, provare a ** usare ** un tale valore come argomento template, come in 'Factorial ', fallirà perché il compilatore potrebbe aver deciso di non rendere questa istanza di 'value' a constexpr. – rwong

1

È possibile utilizzare static const int come dice Nawaz. Immagino che la ragione per cui Scott Myers usa un enum è che il supporto del compilatore per l'inizializzazione in-house dei cost const interi era un po 'limitato quando ha scritto il libro. Quindi un enum era un'opzione più sicura.

2

Per essere più specifici, la "enum hack" esiste perché il modo più corretto di farlo con static const int non era supportato da molti compilatori del tempo. È ridondante nei moderni compilatori.

4

Vedo che le altre risposte trattano bene gli approcci alternativi, ma nessuno ha spiegato perché è richiesto lo enum (o static const int).

In primo luogo, si consideri il seguente modello di non-equivalenti:

#include <iostream> 

int Factorial(int n) 
{ 
    if (n == 0) 
     return 1; 
    else 
     return n * Factorial(n-1); 
} 

int main() 
{ 
    std::cout << Factorial(5) << std::endl; 
    std::cout << Factorial(10) << std::endl; 
} 

Si dovrebbe essere in grado di capire facilmente. Tuttavia, lo svantaggio è che il valore del fattoriale verrà calcolato in fase di esecuzione, vale a dire dopo aver eseguito il programma, il compilatore eseguirà le chiamate e i calcoli della funzione ricorsiva.

L'idea dell'approccio modello è eseguire gli stessi calcoli in fase di compilazione e posizionare il risultato nell'eseguibile risultante. In altre parole, l'esempio che ha presentato risolve a qualcosa di simile:

int main() 
{ 
    std::cout << 120 << std::endl; 
    std::cout << 3628800 << std::endl; 
} 

Ma al fine di ottenere che, bisogna 'trucco' il compilatore a compiere i calcoli. E per farlo, devi lasciare che memorizzi il risultato da qualche parte.

Il enum è esattamente lì per farlo. Cercherò di spiegarlo sottolineando che non funzionerebbe lì.

Se si è tentato di utilizzare un normale int, non funzionerebbe perché un membro non statico come int è significativo solo in un oggetto istanziato. E non puoi assegnare un valore come questo ma invece farlo in un costruttore. Un semplice int non funzionerà.

Hai bisogno di qualcosa che sarebbe accessibile su una classe non documentata. Potresti provare static int ma ancora non funziona.clang darebbe una descrizione abbastanza semplice del problema:

c.cxx:6:14: error: non-const static data member must be initialized out of line 
       static int value=n*Factorial<n-1>::value ; 
         ^ ~~~~~~~~~~~~~~~~~~~~~~~ 

Se effettivamente messo queste definizioni out-of-line, il codice verrà compilato ma si tradurrà in due 0 s. Questo perché questo modulo ritarda il calcolo dei valori all'inizializzazione del programma e non garantisce l'ordine corretto. È probabile che sia stato ottenuto un Factorial<n-1>::value s prima di essere calcolato, e quindi è stato restituito 0. Inoltre, non è ancora ciò che realmente vogliamo.

Infine, se si inserisce static const int lì, funzionerà come previsto. Questo perché static const deve essere calcolato al momento della compilazione, ed è esattamente ciò che vogliamo. Proviamo quindi a digitare di nuovo il codice:

#include <iostream> 

template <unsigned n> 
struct Factorial 
{ 
    static const int value=n*Factorial<n-1>::value ; 
}; 

template <> 
struct Factorial<0> 
{ 
    static const int value=1; 
}; 

int main() 
{ 
    std::cout << Factorial<5>::value << std::endl; 
    std::cout << Factorial<10>::value << std::endl; 
} 

Prima si crea un'istanza Factorial<5>; static const int obbliga il compilatore a calcolare il suo valore al momento del compilatore. In effetti, crea un'istanza del tipo Factorial<4> quando deve calcolare un altro valore. E questo va uno fino a quando non colpisce Factorial<0> dove il valore può essere calcolato senza ulteriori istanziazioni.

Quindi, quello era il modo alternativo e la spiegazione. Spero che sia stato almeno un po 'utile per capire il codice.

Si può pensare a quel tipo di modelli come una sostituzione della funzione ricorsiva che ho postato all'inizio. Basta sostituire:

  • return x; con static const int value = ...,
  • f(x-1) con t<x-1>::value,
  • e if (n == 0) con la specializzazione struct Factorial<0>.

E per il enum stessa, come è stato già sottolineato, è stato utilizzato nell'esempio di applicare lo stesso comportamento static const int. È così perché tutti i valori enum devono essere conosciuti in fase di compilazione, così efficacemente ogni valore richiesto deve essere calcolato in fase di compilazione.