我正在学习C++,目前正在摆弄以下代码:
class Bar;
struct Callback {
virtual void Continue(Bar&) = 0;
};
// ...
void Foo(Bar& _x, Callback& result)
{
// Do stuff with _x
if(/* some condition */) {
// TODO: Force unwind of stack
result.Continue(_x);
return;
}
// Do more stuff with _x
if(/* some other condition */) {
// TODO: Force unwind of stack
result.Continue(_x);
return;
}
// TODO: Force unwind of stack
Bar y; // allocate something on the stack
result.Continue(y);
}
主要思想是,我知道在每个站点
result.Continue
都被调用时,函数Foo
也将返回。因此,可以在调用延续之前将堆栈展开。由于用户代码将以递归方式使用它,因此我担心此代码可能会导致堆栈溢出。据我所知,执行
_x
时,将参数result
和result.Continue
保留在堆栈上,因为仅当Foo
返回时才取消堆栈的堆栈。编辑:
Continue
函数可能(可能会)调用Foo
方法:导致递归。简单地对Continue
而不是Foo
进行尾部调用优化可能会导致堆栈溢出。我该怎么做才能在
Foo
返回之前强制展开堆栈,将result
保留在一个临时(register
?)变量中,然后执行该继续操作?
最佳答案
您可以使用我发现的可解决此问题的设计。该设计假定使用事件驱动程序(但是您可以创建假事件循环)。
为了清楚起见,让我们忘记您的特定问题,而将注意力放在两个对象之间的接口(interface)问题上:发送者对象将数据包发送到接收者对象。发送者总是必须等待接收者完成对任何数据包的处理,然后再发送另一个。该接口(interface)由两个调用定义:
这些调用均未返回任何内容。接收者总是通过调用Done()来报告操作完成。如您所见,此接口(interface)在概念上与您介绍的接口(interface)相似,并且遭受Send()和Done()之间递归的相同问题,可能导致堆栈溢出。
我的解决方案是将作业队列引入事件循环。作业队列是等待分派(dispatch)的事件的 LIFO队列(堆栈)。事件循环将队列顶部的作业视为最大优先级事件。换句话说,当事件循环必须决定要分派(dispatch)哪个事件时,如果队列不为空,并且没有任何其他事件,它将始终分派(dispatch)作业队列中的最高作业。
然后修改上述接口(interface),以使Send()和Done()调用排队。这意味着,当发送方调用Send()时,所有发生的事情是将作业推送到作业队列,并且该作业在由事件循环调度时,将调用接收方的Send()的实际实现。 Done()以相同的方式工作-由接收方调用,它只是推送一个作业,该作业在分派(dispatch)时调用发送方的Done()实现。
了解队列设计如何提供三个主要好处。
这些功能一起在事件驱动程序中启用了优雅的flow-based programming。我已经在BadVPN软件项目中实现了队列设计和基于流的编程,并取得了巨大的成功。
最后,澄清为什么作业队列应该是LIFO。 LIFO调度策略提供了对作业分配顺序的粗粒度控制。例如,假设您正在调用某个对象的某个方法,并且想要在该方法执行后,并且递归地调度了它推送的所有作业之后执行某些操作。您要做的就是在调用此方法之前先完成自己的工作,然后从该事件的事件处理程序中进行工作。
还有一个不错的属性,您始终可以通过使作业出队来取消此推迟的工作。例如,如果此函数执行的某些操作(包括它推送的作业)导致错误并因此破坏了我们自己的对象,则析构函数可以使我们推送的作业出队,从而避免了在执行作业和访问数据时发生崩溃不再存在。
关于c++ - C++强制堆栈内部函数展开,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10064229/