9

我对 Scott Meyers 的“Effective C++”中的第 48 项有一个简短的问题。我只是不明白从下面的书中复制的代码,

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

为什么我必须在模板编程中使用枚举?有没有其他方法可以做到这一点?我在这里先向您的帮助表示感谢。

4

5 回答 5

8

你也可以使用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;
};

这也应该没问题。两种情况下的结果都是一样的。

或者您可以使用现有的类模板,例如std::integral_constant(仅在 C++11 中):

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

template <>
struct Factorial<0> : std::integral_constant<int,1> {};
于 2012-08-11T18:28:16.870 回答
6

我看到其他答案很好地涵盖了替代方法,但没有人解释为什么需要enum(or static const int)。

首先,考虑以下非模板等效项:

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

你应该能够很容易地理解它。然而,它的缺点是阶乘的值将在运行时计算,即在运行程序之后,编译器将执行递归函数调用和计算。

模板方法的想法是在编译时执行相同的计算,并将结果放入生成的可执行文件中。换句话说,您提供的示例可以解决类似问题:

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

但为了实现这一点,您必须“欺骗”编译器执行计算。为了做到这一点,您需要让它将结果存储在某个地方。

enum正是为了做到这一点。我将尝试通过指出在那里不起作用的方法来解释这一点。

如果您尝试使用常规int,它将无法正常工作,因为非静态成员 likeint仅在实例化对象中才有意义。而且你不能像这样给它赋值,而是在构造函数中这样做。平原是int行不通的。

您需要一些可以在未实例化的类上访问的东西。你可以试试static int,但还是不行。clang会给你一个非常简单的问题描述:

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

如果你真的把这些定义放在外面,代码会编译,但会产生两个0s。那是因为这种形式将值的计算延迟到程序的初始化,并且不能保证正确的顺序。很可能Factorial<n-1>::value在计算之前获得了 a ,因此0被返回。此外,它仍然不是我们真正想要的。

最后,如果你把它放在static const int那里,它将按预期工作。那是因为static const必须在编译时计算,而这正是我们想要的。让我们再次输入代码:

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

首先你实例化Factorial<5>static const int强制编译器必须在编译器时计算它的值。实际上,它Factorial<4>在必须计算另一个值时实例化类型。这一直持续到它Factorial<0>到达可以在没有进一步实例化的情况下计算值的位置。

所以,这就是替代方式和解释。我希望它至少对理解代码有所帮助。

您可以将这种模板视为我在开头发布的递归函数的替代品。您只需替换:

  • return x;static const int value = ...,
  • f(x-1)t<x-1>::value,
  • if (n == 0)具有专业化struct Factorial<0>

而对于它enum本身,正如已经指出的那样,它在示例中用于强制执行与static const int. 就像那样,因为所有enum值都需要在编译时知道,所以实际上每个请求的值都必须在编译时计算。

于 2012-08-11T23:21:00.687 回答
2

更具体地说,“枚举黑客”之所以存在,是因为static const int当时许多编译器不支持更正确的做法。它在现代编译器中是多余的。

于 2012-08-11T21:53:05.870 回答
1

你可以static const int像纳瓦兹所说的那样使用。我猜 Scott Myers 使用枚举的原因是编译器对静态 const 整数的类内初始化的支持在他写这本书时有点受限。所以枚举是一个更安全的选择。

于 2012-08-11T21:44:52.370 回答
0

您可以为此使用 int 而不是 static const 它,如下所示:


template<int n>
struct Factorial
{
    int val{ n*Factorial<n - 1>().val };
};

template<>
struct Factorial<0>
{
    int val{1};
};

int main()
{
    cout << "Factorial 5: " << Factorial<5>().val << endl;
}

您还可以使用函数模板而不是结构/类模板:

template<int n>
int fact()
{
    return n*fact<n - 1>();
}

template <>
int fact<0>()
{
    return 1;
}

int main()
{
    cout << "Fact 5: " << fact<5>() << endl;
}
于 2020-05-17T11:00:13.190 回答