c++ - 有没有办法不杀死抛出 std::bad_alloc 的 Qt 应用程序?

标签 c++ qt exception c++11 exception-safety

异常安全在现代 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 之前,我非常希望能收到一些反馈。

  1. 这是个好主意吗?
  2. 这样我的代码会异常安全吗?
  3. 甚至可以用 Qt 编写异常安全代码吗?

最佳答案

这个问题早就解决了,在 Qt 中有一个惯用的解决方案。

所有插槽调用最终都源自:

  • 事件处理程序,例如:

    • 计时器的timeout 信号是由QTimer 处理QTimerEvent 产生的。

    • QObejct 处理 QMetaCallEvent 时产生一个排队的插槽调用。

  • 您可以完全控制的代码,例如:

    • 当您在 mainQThread::runQRunnable::run 的实现中发出信号时。

对象中的事件处理程序总是通过QCoreApplication::notify 到达。因此,您所要做的就是子类化应用程序类并重新实现通知方法。

这确实会影响所有 源自事件处理程序的信号槽调用。具体来说:

  1. 源自事件处理程序的所有信号及其直接附加

    这增加了每个事件的成本,不是每个信号的成本,不是每个时隙的成本。为什么差异很重要?许多控件针对单个事件发出多个信号。 QPushButtonQMouseEvent 作出 react ,可以发出 clicked(bool)pressed() released()toggled(bool),都来自同一个事件。尽管发出了多个信号,notify 只被调用了一次。

  2. 所有排队的插槽调用和方法调用

    它们是通过将 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/

相关文章:

c++ - Clang-CL 警告 strnicmp 已弃用,请使用符合 ISO C 和 C++ 的名称 _strnicmp

c++ - QVector 总是推回 0?

c++ - Qt QString::split() 的相反方法

c++ - QTreeWidget可以跳转到特定行(由QTreeWidgetItem或列指定)吗?

c++ - Visual Studio C++ 异常......怪异

c++ - 为哈希表获取和设置正确重载 [bracket] 运算符

c++ - 简单的 GLUT 库问题

c++ - Qt:检测鼠标是否位于某个小部件上,即使该小部件没有焦点

java - 在 Glassfish 上部署 Web 应用程序时出错

java - 异常类不工作