Exception Safety

开场警告: 异常安全功能尚未完成! 常见情况应该可以工作, 但类仍然可能泄漏甚至崩溃.

Qt 本身不会抛出异常. 相反, 使用错误代码. 此外, 某些类具有用户可见的错误消息, 如 QIODevice::errorString() 或 QSqlQuery::lastError(). 这有历史和实际原因 - 打开异常可以使库大小增加 20% 以上.

以下部分描述了在编译时启用异常支持时 Qt 的行为.

Exception Safe Modules

Containers

Qt 的 容器类 通常是异常中立的. 它们将其包含的类型 T 中发生的任何异常传递给用户, 同时保持其内部状态有效.

示例:


  QList<QString> list;
  ...
  try {
      list.append("hello");
  } catch (...) {
  }
  // list is safe to use - the exception did not affect it.

该规则的例外是在赋值或复制构造期间可能抛出的类型的容器. 对于这些类型, 修改容器以及返回值的函数使用起来不安全:


  MyType s = list.takeAt(2);

如果在对 s 赋值期间发生异常, 则索引 2 处的值已从容器删除, 但尚未分配给 s. 这个值会丢失, 且无法恢复.

正确的写法如下所示:


  MyType s = list.at(2);
  list.removeAt(2);

如果赋值抛出异常, 容器仍将包含该值; 没有发生数据丢失.

注意, 隐式共享的 Qt 类不会抛出其赋值运算符或复制构造函数, 因此上述限制不适用.

Out of Memory Handling

大多数桌面操作系统都会过量使用内存. 这意味着 malloc()operator new 返回一个有效的指针, 即使在分配时没有足够的可用内存. 在此类系统上, 不会引发 std::bad_alloc 类型异常.

在所有其他操作系统上, 如果任何分配失败, Qt 将抛出 std::bad_alloc 类型的异常. 如果系统内存不足或没有足够的连续内存来分配请求的大小, 分配可能会失败.

这个规则有例外情况. 如, QImage 在内存不足时, 会构造一个 null 图像, 而不是抛出异常.

Recovering from Exceptions

目前, 从 Qt 中引发的异常 (例如由于内存不足) 中恢复的唯一支持的用例是, 退出事件循环并在退出应用程序之前进行一些清理.

经典用法如下:


  QApplication app(argc, argv);
  ...
  try {
      app.exec();
  } catch (const std::bad_alloc &) {
      // clean up here, e.g. save the session
      // and close all config files.

      return 0; // exit the application
  }

抛出异常后, 与窗口服务器的连接可能已经关闭. 捕获异常后调用 GUI 相关函数是不安全的.

Exceptions in Client Code

Signals and Slots

从 Qt 的 信号槽函 连接机制调用的槽函数引发的异常, 被视为未定义行为, 除非在槽函数内处理:


  State state;
  StateListener stateListener;

  // OK; the exception is handled before it leaves the slot.
  QObject::connect(&state, SIGNAL(stateChanged()), &stateListener, SLOT(throwHandledException()));
  // Undefined behaviour; upon invocation of the slot, the exception will be propagated to the
  // point of emission, unwinding the stack of the Qt code (which is not guaranteed to be exception safe).
  QObject::connect(&state, SIGNAL(stateChanged()), &stateListener, SLOT(throwUnhandledException()));

如果像常规函数调用一样直接调用槽函数, 则可以使用异常. 这是因为直接调用槽时绕过了连接机制:


  State state;
  StateListener stateListener;

  // ...

  try {
      // OK; invoking slot directly.
      stateListener.throwException();
  } catch (...) {
      qDebug() << "Handling exception not caught in slot.";
  }