带对象的尾递归

时间:2016-12-30 13:44:56

标签: c++ recursion tail-recursion

我有一个递归函数,我想做尾递归。我的实际问题更复杂,并且依赖于上下文。但是我想用这个简单的程序来解决这个问题:

#include <iostream>

struct obj
{
    int n;

    operator int&() { return n; }
};

int tail(obj n)
{
    return tail(obj{ n + 1 > 1000 ? n - 1000 : n + 1 });
}

int main()
{
    tail(obj{ 1 });
}

这似乎很自然,这是尾递归的。但事实并非如此,因为每次都必须调用obj n的析构函数。至少MSVC13(编辑:) 和MSVC15 不优化此功能。如果我用int替换obj并相应地更改调用,它将按预期变为尾递归。

我的实际问题是:除了用obj替换int之外,还有一种简单的方法可以实现这种尾递归吗?我的目标是获得性能优势,因此使用堆分配的内存和new很可能没有帮助。

2 个答案:

答案 0 :(得分:1)

由于您使用的是临时的,我假设您在递归调用后不需要该对象。

一个相当神奇的解决方案是分配一个对象,传递一个指针,并在进行递归调用之前重新分配它,然后传递新构造的对象。

XDocument doc = XDocument.Load("001.xml"); // without "new"

我显然忽略了一些关键的异常安全细节。但是GCC is able to turn tail_impl into a loop,因为它确实是尾递归。

答案 1 :(得分:1)

简答:否。

更长的答案:您可能会找到实现这一目标的方法,但肯定不容易。 由于标准不要求尾部调用优化,因此您无法确定程序的某些细微更改是否会使编译器无法优化代码。

更糟糕的是,请考虑当您需要调试程序时会发生什么。编译器几乎肯定不会使用调试器标志优化高级尾​​调用,这意味着您的程序只能在发布模式下正常工作。这将使程序更难维护。

替代尾递归 只需写一个循环。它总是可以完成,而且很可能会更复杂。它也不使用堆,因此开销会小得多。