2012-08-11 15 views
8

Mam krótkie pytanie o pozycję 48 w "Skuteczne C++" Scotta Meyersa. ja po prostu nie rozumieją kod skopiowany z książki poniżejSzablon w C++, dlaczego muszę używać 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; 
    } 

Dlaczego muszę używać enum w programowaniu szablonu? Czy jest inny sposób na zrobienie tego? Dzięki za pomoc z góry.

Odpowiedz

8

Można użyć static const int również:

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

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

ten powinien być również w porządku. Wynik jest taki sam w obu przypadkach.

Albo można użyć istniejącego szablonu klasy, takie jak std::integral_constant (w C++ 11 tylko) jako:

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

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

To właściwie nie odpowiada na pytanie, dlatego użył słowa "enum". – Puppy

+0

@Nawaz najwyraźniej zna odpowiedź, ale nie określił jej wyraźnie. W niektórych kompilatorach 'statyczna const int' nie jest ** gwarantowana ** stała czasu kompilacji, ponieważ standard pre-C++ 11 nie wymaga od kompilatora do podjęcia wyczerpującej próby jego rozwiązania. Zatem, próba ** użycia ** takiej "wartości" jako argumentu szablonu, jak w 'Factorial ', nie powiedzie się, ponieważ kompilator mógł zdecydować, że nie uczyni tego wystąpienia wartości "constexpr". – rwong

1

Można użyć static const int jak mówi Nawaz. Domyślam się, że powodem, dla którego Scott Myers używa wyliczenia, jest to, że wsparcie kompilatora dla inicjowania w klasie statycznych stałych liczb całkowitych było nieco ograniczone, gdy pisał książkę. Więc enum to bezpieczniejsza opcja.

2

Mówiąc dokładniej, "wyłudzony hack" istnieje, ponieważ poprawniejszy sposób robienia tego z static const int nie był obsługiwany przez wiele kompilatorów czasu. Jest zbędny w nowoczesnych kompilatorach.

4

widzę, że pozostałe odpowiedzi pokrycie alternatywnych podejść dobrze, ale nikt nie wyjaśnił, dlaczego konieczne jest enum (lub static const int).

Po pierwsze, rozważmy następujący zakaz szablonu równoważne:

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

powinieneś być w stanie go zrozumieć łatwo. Jednak wadą jest to, że wartość silni zostanie obliczona w czasie wykonywania, tj. Po uruchomieniu programu kompilator wykona rekursywne wywołania funkcji i obliczenia.

Ideą podejścia szablonowego jest wykonanie tych samych obliczeń w czasie kompilacji i umieszczenie wyniku w wynikowym pliku wynikowym. Innymi słowy, jesteś przykładem prezentowane postanawia coś zarówno:

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

Jednak aby to osiągnąć, trzeba „trik” kompilator do wykonywania obliczeń. Aby to zrobić, musisz pozwolić mu na przechowywanie gdzieś wyniku.

Urządzenie enum jest dokładnie w tym celu. Spróbuję to wytłumaczyć, wskazując, że nie będzie tam działać .

Jeśli próbowałeś użyć zwykłego int, to nie działałoby, ponieważ niestatyczny element, taki jak int, ma znaczenie tylko w instancji obiektu. I nie można przypisać takiej wartości do tego, ale zamiast tego zrobić to w konstruktorze. Zwykły int nie będzie działać.

Potrzebujesz czegoś, co będzie dostępne w nieumiejętnej klasie. Możesz spróbować static int, ale nadal nie działa.clang nie daje dość prosty opis problemu:

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

Jeśli faktycznie umieścić te definicje out-of-line, kod zostanie skompilowany, ale spowoduje to dwie 0 s. Dzieje się tak dlatego, że ta forma opóźnia obliczenie wartości do momentu inicjalizacji programu i nie gwarantuje poprawnej kolejności. Prawdopodobnie przed obliczeniem otrzymano Factorial<n-1>::value s, a więc zwrócono 0. Co więcej, nadal nie jest to, czego naprawdę chcemy.

Wreszcie, jeśli umieścisz tam static const int, będzie działać zgodnie z oczekiwaniami. To dlatego, że static const musi zostać obliczone w czasie kompilacji i właśnie tego chcemy. Załóżmy wpisać kod jeszcze:

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

najpierw utworzyć instancję Factorial<5>; static const int zmusza kompilator do obliczenia jego wartości w czasie kompilacji. W efekcie tworzy instancję typu Factorial<4>, gdy ma obliczyć inną wartość. I to idzie jeden, aż trafi Factorial<0>, gdzie wartość można obliczyć bez dalszych wystąpień.

To była alternatywna droga i wyjaśnienie. Mam nadzieję, że było to co najmniej pomocne w zrozumieniu kodu.

Możesz myśleć o tego rodzaju szablony jako zamiennik funkcji rekurencyjnej, którą zamieściłem na początku. Wystarczy wymienić:

  • return x; z static const int value = ...,
  • f(x-1) z t<x-1>::value,
  • i if (n == 0) ze specjalizacją struct Factorial<0>.

A dla samego enum, jak to już zauważył, że został użyty w przykładzie egzekwować takie samo zachowanie jak static const int. Jest tak, ponieważ wszystkie wartości enum muszą być znane podczas kompilacji, więc efektywnie każda wymagana wartość musi zostać obliczona podczas kompilacji.