我有一个递归函数,我想做尾递归。我的实际问题更复杂,并且依赖于上下文。但是我想用这个简单的程序来解决这个问题:
#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
很可能没有帮助。
答案 0 :(得分:1)
由于您使用的是临时的,我假设您在递归调用后不需要该对象。
一个相当神奇的解决方案是分配一个对象,传递一个指针,并在进行递归调用之前重新分配它,然后传递新构造的对象。
XDocument doc = XDocument.Load("001.xml"); // without "new"
我显然忽略了一些关键的异常安全细节。但是GCC is able to turn tail_impl
into a loop,因为它确实是尾递归。
答案 1 :(得分:1)
简答:否。
更长的答案:您可能会找到实现这一目标的方法,但肯定不容易。 由于标准不要求尾部调用优化,因此您无法确定程序的某些细微更改是否会使编译器无法优化代码。
更糟糕的是,请考虑当您需要调试程序时会发生什么。编译器几乎肯定不会使用调试器标志优化高级尾调用,这意味着您的程序只能在发布模式下正常工作。这将使程序更难维护。
替代尾递归 只需写一个循环。它总是可以完成,而且很可能会更复杂。它也不使用堆,因此开销会小得多。