Q1。为什么使用回调函数?
Q2。回调是邪恶的吗?有趣的是那些 谁知道,对别人来说是一场噩梦。
Q3。回调的替代方法吗?
答案 0 :(得分:26)
回调减少了耦合 - 被调用方传递了一些指针,它不知道它背后是什么。回调是如此幸运的解决方案,它们非常普遍。
例如,请查看sqlite3_exec()。您给它一个查询和可选的回调。它执行查询并在检索时调用每一行的回调。现在,SQLite的业务是快速执行查询并且资源消耗低,只有您的业务才能按照您的喜好处理检索到的结果。你可以将它们添加到一个容器中并稍后处理它们,或者你可以一个一个地立即处理它们,或者你可以将它们传真到某个地方并期望另一方传回它们 - SQLite并不关心,它是完全抽象的,可以只是做好自己的工作。
答案 1 :(得分:4)
无论是否使用“C ++中的回调增加耦合”,我建议使用事件处理程序样式,尤其是类似于事件的事件。例如,具体的设计模式而不是回调概念如下:
class MyClass
{
public:
virtual bool OnClick(...) = 0;
virtual bool OnKey(...) = 0;
virtual bool OnTimer(...) = 0;
virtual bool OnSorting(...) = 0
...
};
您可能仍然认为上述函数是回调函数,但在将它们视为已知的设计模式时,您不会感到困惑,因为您正在进行OO并编写C ++。
Effo UPD @ 2009nov13 - 典型案例:框架,事件系统或并发编程模型等。以下示例应该有用。
框架控制整体流程,因为好莱坞原则声明“不要打电话给我们,我们会打电话给你。” (这就是“回调”意味着什么),而对于普通函数或lib,调用者控制流。
一个着名的C框架是Linux内核,Linux驱动程序编写器知道他/她实现了“struct file_operations”,其中“read()”表示OnRead(),“write()”表示OnWrite( )等。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
...
};
但最简单的框架示例应该是:
The Framework | A developer to do
----------------------------+--------------------------------------
| class MyClass : public Actor
| {
| public:
pApplication->Init(...); | virtual bool OnInit(...) {}
pApplication->Run(...); | virtual int OnRun(...) {}
pApplication->Quit(...); | virtual void OnQuit(...) {}
| ...
| };
和pApplication-> Init()将调用pActor-> OnInit,pApplication-> Run()调用pActor-> OnRun(),依此类推。大多数Windows GUI开发人员都经历过实现OnMouseClick()或OnButtonPress()等。
我同意这个线程的其他答案,他们相应地基于视点给出了正确的解释,例如分层方法中的处理程序,通用回调或异步操作等等。你有什么想法可以适合你。
答案 2 :(得分:3)
Q3。回调的替代方法吗?
我更喜欢函数形式的回调。例如这种事情:
class WidgetContainerOrProcessorOfSomeSort
{
public:
struct IWidgetFunctor
{
virtual bool operator()( const Widget& thisWidget )=0;
};
....
bool ProcessWidgets( IWidgetFunctor& callback )
{
.....
bool continueIteration = callback( widget[ idxWidget ] );
if ( !continueIteration ) return false;
.....
}
};
struct ShowWidgets : public WidgetContainerOrProcessorOfSomeSort::IWidgetFunctor
{
virtual bool operator(){ const Widget& thisWidget }
{
thisWidget.print();
return true;
}
};
WidgetContainterOrProcessorOfSomeSort wc;
wc.ProcessWidgets( ShowWidgets() );
对于简单的样本,它似乎总是有点冗长,但在现实世界中,我发现它比想要记住如何构造复杂函数指针声明要容易得多: - )
答案 3 :(得分:1)
Boost.Function早先发布了指向Boost的帖子。如果您正在寻找更通用的回调解决方案,例如附加到同一回调的多个函数或类似的回调,请考虑使用Boost.Signals。这个名字来自信号和插槽,这是现在有些人引用回调的方式,特别是对于GUI。
答案 4 :(得分:0)
回调用于调用异步操作,即调用者按照自己的方式在不同的线程中运行的代码。您需要一些机制来了解异步操作何时完成。
他们为什么会变邪恶?与任何其他编程资源一样,它们在明智地使用时非常有用。实际上,Windows API和.NET框架广泛使用回调。
不了解C ++,但在.NET世界中,同步化对象和事件是替代方案。
答案 5 :(得分:0)
Q1。如果您使用分层方法,其中较高级别调用较低级别并通过回调从较低级别获得反馈,则需要回调 Q2。采取一些预防措施时,它们并不比例如例外。在某些方面,它们是相似的。 Q3。更多耦合:较低级别的人知道更高。
说明:
- 简单方法(每个回调1个回调处理程序):通过接口注册CallbackHandler对象
- 使用信号(QT,boost,...),并确保每次回调使用唯一信号以增强可追溯性
编辑:示例:
用户调用ProtocolHandler发送消息,ProtocolHandler调用用户发送回复:相互依赖。
分层:用户级别较高,ProtocolHandler级别较低。在启动时,它会为回复注册一个回调,并调用ProtocolHandler来发送消息。 ProtocolHandler使用回调来发送回复:只有用户依赖于ProtocolHandler。
答案 6 :(得分:0)
如果我们在C ++上下文中,请考虑使用(例如generalized callbacks)。
基本的行为思想是回调(类方法)可以是任何名称,并且不需要从回调执行程序知道的某个类派生回调。
回调的唯一限制是输入参数和返回值。所以这会将夫妻减少到零......:)
UDP:
@EffoStaff的答案Effo是一个回调应该是特定类(派生)并具有修正名称的示例。在一般化回调的背景下,所有这些“限制”都不存在。
答案 7 :(得分:0)
在极少数情况下,使用Win32样式的回调会注意“模糊的重入”问题(如果通过回调你只是意味着非常基本地将函数作为arg传递给另一个并发问题是不可想象的函数)。登记/>
Joe Duffy的“Windows上的并发编程”的优秀索引列出了“reentrancy”下的5个主题,这些主题通常是“围绕Rone-Hood的谷仓回归”概念的特殊化 - 换言之,来自最高评级-answer“被调用的一方通过一些指针,它不知道它背后是什么。”,“不知道”有时可以引导 - 罗宾汉的谷仓。
我刚刚说的任何内容都不是特定于回调,但如果被调用方在Duffy的一个场景中遇到困难,则可能发生“模糊的重入”。换句话说,隐含在“回调”的概念中,你似乎会在同一个线程中回调,这是如何发生的,它是如何同步的。
如果你谷歌在Duffy的书名上并在你的搜索中添加Callback这个词,那么你会得到超过10页的点击。