简体   繁体   中英

Call recursive function in std::function

I just wrote a thread pool using C++11/14 std::thread objects and use tasks in the worker queue. I encountered some weird behaviour when calling recursive functions in lambda expressions. The following code crashes if you implement fac() in a recursive fashion (both with clang 3.5 and 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;
}

It does, however, work fine with the iterative version above. What am I missing?

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

The first time through that loop, i is going to be set to zero, so you're executing:

fac(0);

Doing so with the recursive definition:

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

means that the else block will execute, and hence x will wrap around to whatever the maximum size_t value is (as it's unsigned).

Then it's going to run from there down to 1 , consuming one stack frame each time. At a minimum, that's going to consume 65,000 or so stack frames (based on the minimum allowed value of size_t from the standards), but probably more, much more.

That's what causing your crash. The fix is relatively simple. Since 0! is defined as 1 , you can simply change your statement to be:

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

But you should keep in mind that recursive functions are best suited to those cases where the solution space reduces quickly, a classic example being the binary search, where the solution space is halved every time you recur.

Functions that don't reduce solution space quickly are usually prone to stack overflow problems (unless the optimiser can optimise away the recursion). You may still run into problems if you pass in a big enough number and it's no real different to adding together two unsigned numbers with the bizarre (though I actually saw it put forward as a recursive example many moons ago):

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

So, in your case, I'd stick with the iterative solution, albeit making it bug-free:

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

Both definitions have different behavior for x=0 . The loop will be fine as it uses the less-than operator:

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

However,

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

Results in a quasi-infinite loop as x == 1 is false and x-1 yields the largest possible value of std::size_t (typically 2 64 -1).

The recursive version does not take care of the case for x == 0 .

You need:

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

or

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM