异常安全

Qt中的异常安全指南。

初步警告:异常安全性功能尚未完全实现!常见情况应该可以正常工作,但类可能仍然会泄漏甚至崩溃。

Qt 本身不会抛出异常。相反,它使用错误代码。此外,一些类有用户可见的错误消息,例如 QIODevice::errorString() 或 QSqlQuery::lastError()。这是有历史和实践原因的——启用异常可能会使库的大小增加超过 20%。

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

异常安全模块

容器

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类在其赋值运算符或复制构造函数中不会抛出异常,因此上述限制不适用。

内存不足处理

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

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

该规则的例外情况已记录在案。例如,如果内存不足,QImage 构造函数将创建一个空图像,而不是抛出异常。

从异常中恢复

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

典型用例:

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

    return EXIT_FAILURE; // exit the application
}
...
return ret;

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

客户端代码中的异常

信号与槽

从由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.";
}