这个问题早就解决了,并且在 Qt 中有一个惯用的解决方案。
所有插槽调用最终都来自:
事件处理程序,例如:
您可以完全控制的代码,例如:
main
当您在, or from QThread::run
, or from的实现中发出信号时QRunnable::run
。
对象中的事件处理程序总是通过QCoreApplication::notify
. 因此,您所要做的就是继承应用程序类并重新实现 notify 方法。
这确实会影响源自事件处理程序的所有信号槽调用。具体来说:
源自事件处理程序的所有信号及其直接附加的插槽
这增加了每个事件的成本,而不是每个信号的成本,也不是每个插槽的成本。为什么差异很重要?许多控件在单个事件中发出多个信号。An QPushButton
, 对 a 的反应QMouseEvent
可以发出clicked(bool)
, pressed()
or released()
, and toggled(bool)
, 都来自同一个事件。尽管发出了多个信号,但notify
只调用了一次。
所有排队的槽调用和方法调用
它们是通过将 a 分派QMetaCallEvent
给接收者对象来实现的。调用由 执行QObject::event
。由于涉及事件传递,notify
因此使用。成本是每个调用调用(因此它是每个插槽)。如果需要,可以轻松降低此成本(参见实施)。
如果您不是从事件处理程序发出信号 - 例如,从您的main
函数内部,并且插槽是直接连接的,那么这种处理事情的方法显然不起作用,您必须将信号发射包装在 try /catch 块。
由于QCoreApplication::notify
为每个交付的事件调用,此方法的唯一开销是 try/catch 块的成本和基本实现的方法调用。后者很小。
前者可以通过仅将通知包装在标记对象上来缓解。这需要在不影响对象大小的情况下完成,并且不涉及在辅助数据结构中的查找。这些额外成本中的任何一个都将超过没有抛出异常的 try/catch 块的成本。
“标记”需要来自对象本身。有一种可能:QObject::d_ptr->unused
。唉,事实并非如此,因为该成员没有在对象的构造函数中初始化,所以我们不能依赖它被清零。使用这种标记的解决方案需要对 Qt 进行适当的小改动(将unused = 0;
行添加到QObjectPrivate::QObjectPrivate
)。
代码:
template <typename BaseApp> class SafeNotifyApp : public BaseApp {
bool m_wrapMetaCalls;
public:
SafeNotifyApp(int & argc, char ** argv) :
BaseApp(argc, argv), m_wrapMetaCalls(false) {}
void setWrapMetaCalls(bool w) { m_wrapMetaCalls = w; }
bool doesWrapMetaCalls() const { return m_wrapMetaCalls; }
bool notify(QObject * receiver, QEvent * e) Q_DECL_OVERRIDE {
if (! m_wrapMetaCalls && e->type() == QEvent::MetaCall) {
// This test is presumed to have a lower cost than the try-catch
return BaseApp::notify(receiver, e);
}
try {
return BaseApp::notify(receiver, e);
}
catch (const std::bad_alloc&) {
// do something clever
}
}
};
int main(int argc, char ** argv) {
SafeNotifyApp<QApplication> a(argc, argv);
...
}
请注意,我完全忽略在任何特定情况下处理std::bad_alloc
. 仅仅处理它并不等于异常安全。