异常安全在现代 C++ 中非常重要。
已经有一个关于异常安全的大问题 here . 所以我不是在谈论一般的异常安全。我实际上是在谈论 C++ 中 Qt 的异常安全性。还有一个question关于 Stack Overflow 上的 Qt 异常安全,我们有 Qt documentation .
在阅读了我能找到的关于 Qt 异常安全的所有内容之后,我真的觉得用 Qt 实现异常安全是非常困难的。因此,我自己不会抛出任何类型的异常。
真正的问题是 std::bad_alloc:
- Qt 文档指出从 Qt 的信号槽连接机制调用的槽中抛出异常被视为未定义行为,除非它在槽内处理。
- 据我所知,Qt 中的任何插槽都可能抛出 std::bad_alloc。
在我看来,唯一合理的选择是在抛出 std::bad_alloc 之前退出应用程序(我真的不想进入未定义行为领域)。
实现此目的的一种方法是重载 operator new and:
- 如果在 GUI 线程中发生分配失败:退出(终止)应用程序。
- 如果在另一个线程中发生分配失败,则抛出一个 std::bad_alloc。
在编写 operator new 之前,我非常希望能收到一些反馈。
- 这是个好主意吗?
- 这样我的代码会异常安全吗?
- 甚至可以用 Qt 编写异常安全代码吗?
最佳答案
这个问题早就解决了,在 Qt 中有一个惯用的解决方案。
所有插槽调用最终都源自:
事件处理程序,例如:
计时器的
timeout
信号是由QTimer
处理QTimerEvent
产生的。QObejct
处理QMetaCallEvent
时产生一个排队的插槽调用。
您可以完全控制的代码,例如:
- 当您在
main
、QThread::run
或QRunnable::run
的实现中发出信号时。
- 当您在
对象中的事件处理程序总是通过QCoreApplication::notify
到达。因此,您所要做的就是子类化应用程序类并重新实现通知方法。
这确实会影响所有 源自事件处理程序的信号槽调用。具体来说:
源自事件处理程序的所有信号及其直接附加槽
这增加了每个事件的成本,不是每个信号的成本,不是每个时隙的成本。为什么差异很重要?许多控件针对单个事件发出多个信号。
QPushButton
对QMouseEvent
作出 react ,可以发出clicked(bool)
、pressed()
或released()
和toggled(bool)
,都来自同一个事件。尽管发出了多个信号,notify
只被调用了一次。所有排队的插槽调用和方法调用
它们是通过将
QMetaCallEvent
分派(dispatch)给接收者对象来实现的。该调用由QObject::event
执行。由于涉及事件传递,所以使用了notify
。成本是每次调用调用(因此它是每个时隙)。如果需要,可以轻松降低此成本(参见实现)。
如果您发出的信号不是来自事件处理程序 - 比如说,来自您的 main
函数内部,并且插槽是直接连接的,那么这种处理事情的方法显然行不通,您必须将信号发射包装在 try/catch block 中。
由于 QCoreApplication::notify
为每个传递的事件调用,此方法的唯一开销是 try/catch block 和基本实现的方法调用的成本。后者很小。
前者可以通过仅将通知包装在标记的对象上来缓解。这需要在不增加对象大小的情况下完成,并且不涉及在辅助数据结构中进行查找。这些额外成本中的任何一个都将超过没有抛出异常的 try/catch block 的成本。
“标记”需要来自对象本身。那里有可能:QObject::d_ptr->unused
。 las,事实并非如此,因为该成员没有在对象的构造函数中初始化,所以我们不能指望它被清零。使用此类标记的解决方案需要对 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
是否有意义。仅仅处理它不等于异常安全。
关于c++ - 有没有办法不杀死抛出 std::bad_alloc 的 Qt 应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22474907/