2012-08-11 14 views
8

Tengo una pregunta rápida sobre el artículo 48 en "Effective C++" de Scott Meyers. Simplemente no entender el código copiado del libro a continuación,Plantilla en C++, ¿por qué tiene que usar 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; 
    } 

¿Por qué tengo que utilizar enumeración en la programación de la plantilla? ¿Hay alguna otra manera de hacerlo? Gracias por la ayuda con anticipación.

Respuesta

8

Usted podría utilizar static const int también:

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

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

Esto debería estar bien también. El resultado es el mismo en ambos casos.

O puede utilizar la plantilla de clase existentes, como std::integral_constant (en C++ 11 solamente) como:

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

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

Esto no responde la pregunta, por lo que usó 'enum'. – Puppy

+0

@Nawaz aparentemente sabe la respuesta, pero no lo dijo claramente. En algunos compiladores, 'static const int' ** no está garantizado ** una constante en tiempo de compilación, porque el estándar anterior a C++ 11 no requiere que el compilador haga un intento exhaustivo de resolverlo. Por lo tanto, tratar de ** usar ** un 'valor' como un argumento de plantilla, como en' Factorial ', fallará porque el compilador puede haber decidido no hacer de esta instancia de' valor' un constexpr. – rwong

1

Puede utilizar static const int como dice Nawaz. Supongo que la razón por la que Scott Myers usa una enumeración es que el soporte del compilador para la inicialización en clase de los enteros estáticos const fue un poco limitado cuando escribió el libro. Entonces una enumeración era una opción más segura.

2

Para ser más específicos, el "enum hack" existe porque muchos compiladores de la época no soportaban la forma más correcta de hacerlo con static const int. Es redundante en los compiladores modernos.

4

Veo que las otras respuestas cubren bien los enfoques alternativos, pero nadie explica por qué es necesario enum (o static const int).

En primer lugar, consideremos el siguiente plantilla no equivalentes:

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

Usted debe ser capaz de entenderlo fácilmente. Sin embargo, su desventaja es que el valor del factorial se calculará en tiempo de ejecución, es decir, después de ejecutar su programa, el compilador ejecutará las llamadas y cálculos de la función recursiva.

La idea del enfoque de plantilla es realizar los mismos cálculos en tiempo de compilación y colocar el resultado en el ejecutable resultante. En otras palabras, el ejemplo que presenta el resuelve algo parecido:

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

Pero para lograr eso, usted tiene que 'truco' el compilador en la realización de los cálculos. Y para hacer eso, debes dejar que almacene el resultado en alguna parte.

El enum está ahí exactamente para hacer eso. Trataré de explicarlo indicando qué no funcionaría allí.

Si ha intentado utilizar un int normal, no funcionaría porque un miembro no estático como int tiene sentido solo en un objeto instanciado. Y no puede asignarle un valor así, sino que hacerlo en un constructor. Un simple int no funcionará.

Necesita algo que sea accesible en una clase desinstalada en su lugar. Puede intentar static int pero todavía no funciona.clang le daría una descripción bastante sencillo 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 ; 
         ^ ~~~~~~~~~~~~~~~~~~~~~~~ 

Si realmente poner esas definiciones fuera de línea, el código se compilará pero dará lugar a dos 0 s. Esto se debe a que este formulario retrasa el cálculo de valores para la inicialización del programa y no garantiza el orden correcto. Es probable que se haya obtenido Factorial<n-1>::value s antes de calcularse y, por lo tanto, se devolvió 0. Además, todavía no es lo que realmente queremos.

Por último, si pone static const int allí, funcionará como se esperaba. Eso es porque static const tiene que calcularse en el tiempo de compilación, y eso es exactamente lo que queremos. Vamos escriba el código de nuevo:

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

En primer lugar se ejemplariza Factorial<5>; static const int obliga al compilador a calcular su valor en el momento del compilador. Efectivamente, crea el tipo Factorial<4> cuando tiene que calcular otro valor. Y esto va uno hasta que llegue a Factorial<0> donde el valor puede calcularse sin más instanciaciones.

Entonces, esa era la forma alternativa y la explicación. Espero que sea al menos un poco útil para entender el código.

Puede pensar en ese tipo de plantillas como un reemplazo de la función recursiva que publiqué al principio. Usted acaba de cambiar:

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

Y para el enum en sí, como ya se señaló, fue utilizado en el ejemplo para hacer cumplir el mismo comportamiento que static const int. Es así porque todos los valores enum deben conocerse en tiempo de compilación, por lo que todos los valores solicitados deben calcularse efectivamente en tiempo de compilación.

Cuestiones relacionadas