我正在尝试使此函数尾部递归,以便可以使用它来处理大量事件而不会导致堆栈溢出。我确保将递归调用放在函数的最后一行,但是它仍然使递归调用充满了调用堆栈。
我还需要做其他事情以使其尾部递归吗,还是我的编译器不知道如何优化它?
我应该放弃此功能,而是使用循环吗?
template <class Csi>
void GetEvents(EventHandle handle, vector<int> desiredCodes, vector<EventHandle> &events, Csi &csi)
{
if (handle == INVALID_HANDLE)
{
return;
}
int code = csi.GetEventCode(handle);
bool codeSatisfiesSearch = (find(desiredCodes.begin(), desiredCodes.end(), code) != desiredCodes.end());
if (codeSatisfiesSearch)
{
events.push_back(handle);
handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
}
else
{
handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
}
return GetEvents(handle, desiredCodes, events, csi);
}
答案 0 :(得分:7)
从表面上考虑这个问题。
在当前形式下,由于vector<int> desiredCodes
是按值传递的,因此该代码不适合TCO。递归调用后需要调用者销毁局部向量,因此尾部调用优化不是一个选择。
当我更改代码以通过const引用传递矢量时,我注意到clang
的最新版本确实优化了尾调用:https://gcc.godbolt.org/z/qM7QVv
但是,gcc
仍然没有:https://gcc.godbolt.org/z/u7yIvR。我注意到是push_back
阻止了gcc的优化-注释掉后,递归调用就消除了。
将events.push_back(handle)
替换为
events.resize(events.size() + 1);
events[events.size() - 1] = handle;
所有这些都表明,C ++中的TCO并不是要依赖的东西,因为它非常脆弱并且取决于不可预测的因素。您偶尔会获得一笔可观的奖金,但您却无法以此为基础进行设计。
如果TCO对您(例如,对我很感兴趣)感兴趣,那么您将拥有更多可预测的语言(例如C)或函数式样式束,这将带来更好的运气。
答案 1 :(得分:0)
如果您使用编译器标志将其打开,则现代编译器可以执行此优化。我将您的代码修改为以下代码,以使其to compile on godbolt.org:
#include <algorithm>
#include <stddef.h>
#include <vector>
class EventHandle {
public:
constexpr EventHandle() : dummy(~0U) {}
constexpr auto operator==(const EventHandle& x) const
{
return dummy == x.dummy;
}
private:
unsigned dummy;
};
constexpr EventHandle INVALID_HANDLE = EventHandle();
class Csi {
public:
int GetEventCode(EventHandle) const;
EventHandle FindNextEventEx( EventHandle, const int*, int, size_t ) const;
};
void GetEvents(EventHandle handle, const std::vector<int>& desiredCodes, std::vector<EventHandle>& events, Csi &csi)
{
if (handle == INVALID_HANDLE)
{
return;
}
int code = csi.GetEventCode(handle);
bool codeSatisfiesSearch = (std::find(desiredCodes.begin(), desiredCodes.end(), code) != desiredCodes.end());
if (codeSatisfiesSearch)
{
events.push_back(handle);
handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
}
else
{
handle = csi.FindNextEventEx(handle, &desiredCodes[0], 0, desiredCodes.size());
}
return GetEvents(handle, desiredCodes, events, csi);
}
我只做了两个重大更改。我为class
和EventHandle
提供了存根Csi
定义,以便编译器完全生成代码。我也将desiredCodes
和events
更改为引用参数,以便编译器不会复制。 (这改变了语义:该函数现在更改了原始的events
对象。您可能希望将其声明为std::vector<EventHandle>&& events
,以便更明确地掩盖输入参数。)I also added std::
to STL types and algorithms. < / p>
如果将MSVC 19.22 x64赋予/Ox
标志,则它将优化为尾递归。 GCC 9.2 x86_64需要-O2
或更高版本。 Clang 8.0.0和ICC 19.0.1都需要-O
或更高版本。例如,使用-O
,ICC代码清单的第213行会优化为jmp
。
jmp GetEvents(EventHandle, std::vector<int, std::allocator<int> > const&, std::vector<EventHandle, std::allocator<EventHandle> >&, Csi&) #44.12
没有优化标志,第1724行执行递归非尾调用。
call GetEvents(EventHandle, std::vector<int, std::allocator<int> > const&, std::vector<EventHandle, std::allocator<EventHandle> >&, Csi&) #44.12