通过引用捕获异常危险吗?

时间:2015-10-28 09:39:50

标签: c++ exception

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

void some_function() {
    throw std::exception("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()以便将它放在动态内存中吗?

4 个答案:

答案 0 :(得分:93)

const引用确实是安全的 - 并且建议使用。

  

e实际上放在some_function()

的堆栈上

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

  

[except.throw] 15.1 / 4:异常对象的内存以未指定的方式分配,除非在3.7.4.1中说明。异常   在通过除了重新抛出之外的任何方式退出异常的最后一个活动处理程序之后,对象被销毁,或者引用异常对象的std :: exception_ptr(18.8.5)类型的最后一个对象被销毁,以较晚者为准。 / p>

如果为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]);
        ...
    }
}

答案 1 :(得分:22)

通过引用捕获,否则你无法获得正确的动态类型的对象。至于它的生命周期,标准保证在[except.throw]

  

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

答案 2 :(得分:18)

通过const引用捕获完全应该如何捕获异常。异常对象不一定生活在堆栈上。编译器负责使这项工作具有适当的魔力。

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

建议您根据需要抛出std::runtime_errorstd::logic_error或从中派生的类:

  • logic_error当来电者请求了服务设计参数之外的内容时。
  • runtime_error当来电者请求合理的内容但外部因素阻止您尊重请求时。

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

答案 3 :(得分:8)

来自enter image description here

  

抛出异常copy-initializes(8.5,12.8)一个临时对象,   称为异常对象。临时是一个左值,习惯了   初始化匹配处理程序中声明的变量(15.3)。如果   异常对象的类型是不完整的类型或   指向除(可能是cv-qualified)void之外的不完整类型的指针   该计划格式不正确。

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