在std :: function中调用递归函数

时间:2014-12-08 01:31:12

标签: c++ c++11 recursion c++14

我刚刚使用C ++ 11/14 std :: thread对象编写了一个线程池,并使用了工作队列中的任务。在lambda表达式中调用递归函数时遇到了一些奇怪的行为。如果以递归方式实现fac()(包括clang 3.5和gcc 4.9),以下代码会崩溃:

#include <functional>
#include <vector>

std::size_t fac(std::size_t x) {
    // This will crash (segfault).
    // if (x == 1) return 1;
    // else return fac(x-1)*x;

    // This, however, works fine.
    auto res = 1;
    for (auto i = 2; i < x; ++i) {
        res *= x;
    }

    return res;
}

int main() {
    std::vector<std::function<void()> > functions;

    for (auto i = 0; i < 10; ++i) {
        functions.emplace_back([i]() {  fac(i); });
    }

    for (auto& fn : functions) {
        fn();
    }

    return 0;
}
但是,它确实适用于上面的迭代版本。我错过了什么?

3 个答案:

答案 0 :(得分:4)

for (auto i = 0; i < 10; ++i) {
    functions.emplace_back([i]() {  fac(i); });

通过该循环的第一个时间,i将被设置为零,因此您正在执行:

fac(0);

使用递归定义:

if (x == 1) return 1;
else return fac(x-1)*x;

表示else块将执行,因此x将回绕到最大size_t值(因为它是无符号的)。

然后它将从那里运行到1,每次消耗一个堆栈帧。在最小值,将消耗65,000左右的堆栈帧(基于标准中size_t的最小允许值),但可能更多,更多。

导致崩溃的原因。修复相对简单。由于0!定义为1,因此您只需将语句更改为:

if (x <= 1)
    return 1;
return fac (x-1) * x;

但是你应该记住,递归函数最适合解决方案空间快速减少的情况,一个典型的例子是二进制搜索,每次重复时解决方案空间减半

快速减少解决方案空间的函数通常容易出现堆栈溢出问题(除非优化器可以优化递归)。如果传入一个足够大的数字,你可能仍会遇到问题,并且将两个无符号数字加在一起并没有什么不同(虽然我实际上看到它在许多月前被提出作为一个递归的例子): / p>

def addu (unsigned a, b):
    if b == 0:
        return a
    return addu (a + 1, b - 1)

所以,在你的情况下,我会坚持使用迭代解决方案,尽管它没有错误:

auto res = 1;
for (auto i = 2; i <= x; ++i)   // include the limit with <=.
    res *= i;                   // multiply by i, not x.

答案 1 :(得分:3)

两个定义对x=0都有不同的行为。循环将很好,因为它使用小于运算符:

auto res = 1;
for (auto i = 2; i < x; ++i) {
    res *= x;
}

然而,

if (x == 1) return 1;
else return fac(x-1)*x;

在一个准无限循环中导致x == 1为假,x-1产生std::size_t的最大可能值(通常为2 64 -1)。

答案 2 :(得分:1)

递归版本不会处理x == 0的情况。

你需要:

std::size_t fac(std::size_t x) {
    if (x == 1 || x == 0 ) return 1;
    return fac(x-1)*x;
}

std::size_t fac(std::size_t x) {
    if (x == 0 ) return 1;
    return fac(x-1)*x;
}