通过引用捕获异常是否危险

Is catching an exception by reference dangerous?

本文关键字:是否 危险 捕获异常 引用      更新时间:2023-10-16

请看一下以下异常抛出和捕获:

void some_function() {
    // Was std::exception("message") in original post, which won't compile
    throw std::runtime_error("some error message"); 
}
int main(int argc, char **argv) {
    try {
        some_function();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        exit(1);
    }
    return 0;
}

通过引用捕获引发的异常是否安全?

我担心的是,异常e实际上放在some_function()堆栈上。但some_function()刚刚回来,导致e被破坏。所以实际上现在e指向一个被破坏的对象。

我的担忧正确吗?

传递异常而不按值复制它的正确方法是什么?我应该扔new std::exception()以便将其放置在动态内存中吗?

通过

const参考捕获确实是安全的 - 并且建议这样做。

"e其实是放在some_function()堆上">

不,不是...实际抛出的对象是在为异常处理机制保留使用的未指定内存区域中创建的:

[except.throw] 15.1/4:异常对象的内存以未指定的方式分配,但 3.7.4.1 中所述除外。 例外 在异常的最后一个剩余活动处理程序通过重新引发以外的任何方式退出或引用异常对象的最后一个 std::exception_ptr (18.8.5( 类型的对象被销毁后,对象将被销毁,以较晚者为准。

如果指定了一个局部变量来throw ,则在必要时将其复制到该变量(优化器可能能够直接在另一个内存中创建它(。 这就是为什么...

15.1/5 当抛出的对象是类对象时,为复制初始化和析构函数选择的构造函数 应可访问,即使省略了复制/移动操作 (12.8(。


如果没有点击,模糊地想象像这样实现可能会有所帮助:

// implementation support variable...
thread__local alignas(alignof(std::max_align_t))
    char __exception_object[EXCEPTION_OBJECT_BUFFER_SIZE];
void some_function() {
    // throw std::exception("some error message");
    // IMPLEMENTATION PSEUDO-CODE:
    auto&& thrown = std::exception("some error message");
    // copy-initialise __exception_object...
    new (&__exception_object) decltype(thrown){ thrown };
    throw __type_of(thrown);
    // as stack unwinds, _type_of value in register or another
    // thread_local var...
}
int main(int argc, char **argv)
{
    try {
        some_function();
    } // IMPLEMENTATION:
      // if thrown __type_of for std::exception or derived...
      catch (const std::exception& e) {
        // IMPLEMENTATION:
        // e references *(std::exception*)(&__exception_object[0]);
        ...
    }
}

您必须通过引用进行捕获,否则您不可能获得对象的正确动态类型。至于其使用寿命,该标准保证,在[except.throw]

异常

的最后一个剩余活动处理程序通过重新引发以外的任何方式退出后,或者引用异常对象的最后一个 std::exception_ptr (18.8.5( 类型的对象被销毁,以较晚者为准

通过常量引用捕获正是捕获异常的方式。异常对象不一定存在于"堆栈上"。编译器负责适当的魔术来完成这项工作。

另一方面,您的示例无法编译,因为std::exception可能只是默认构造或复制构造的。在这种情况下,what() 方法将返回指向空(c 样式(字符串的指针,这不是特别有用。

建议你根据需要抛出一个std::runtime_errorstd::logic_error,或者从中派生一个类:

  • logic_error调用方请求了超出服务设计参数的内容。
  • runtime_error当呼叫者请求了一些合理的东西,但外部因素阻止您满足请求时。

http://en.cppreference.com/w/cpp/error/exception

from except.throw:

抛出异常副本初始化(8.5,12.8(一个临时对象, 调用异常对象。临时是一个左值,用于 初始化在匹配处理程序 (15.3( 中声明的变量。如果 异常对象的类型将是不完整的类型或 指向(可能符合 CV 条件的(void 以外的不完整类型的指针 程序格式不正确。

这是抛出异常的行为,将异常对象复制到任何堆栈之外的异常区域中。因此,通过引用捕获异常是完全合法且可取的,因为异常对象生存期将延长到最后一个可能的catch()