【问题标题】:Is catching an exception by reference dangerous?通过引用捕获异常是否危险?
【发布时间】:2016-01-28 00:28:07
【问题描述】:

请看下面的异常抛出和捕获:

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() 扔到动态内存中吗?

【问题讨论】:

  • 请注意,std::exception 的字符串构造函数不是标准的,这是一个 MS(符合?)扩展。 stackoverflow.com/questions/5157206/…
  • 那么如何将消息分配给异常,以便使用what() 打印?
  • @SomethingSomething 您将使用std::exception 的派生类,它允许将消息传递给构造函数。例如std::runtime_error。抛出子类型的约定正是你应该通过 (const) 引用捕获的原因。
  • 要编写可移植的代码,您必须从std::exception 继承并编写自己的what() 方法,即std::exception 中的虚拟方法
  • @SomethingSomething 两种方式:1. 使用提供构造函数和字符串消息状态的std::runtime_errorstd::logic_error 或2. 自己做。查看此问题的答案:stackoverflow.com/questions/8152720/…

标签: c++ exception


【解决方案1】:

通过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]);
        ...
    }
}

【讨论】:

  • 我认为 15.1.3 和 15.1.3 一样重要,甚至更多。特别是“抛出异常复制初始化(8.5,12.8)临时对象[...]”,所以e永远不会指向抛出的对象,而是指向一个副本——异常对象——用于初始化catch的参数。
  • @luk32:我不相信:重要的是异常对象的生命周期比展开回相关catch 语句的堆栈更长——15.1.3 有什么意义那?不过,如果您还没有投票,请随时支持 Sigismodo 的回答。您自己对 15.1.3 的解释/结论是有缺陷的恕我直言 - 异常对象的创建可能会被忽略,因此没有必要两个不同的对象可以准确地制作诸如“从不指向抛出的对象,而是指向副本”之类的语句。
  • 嗯.. 好吧,让我收回一点,这同样重要。我的观点是,无论抛出什么,都可能在 catch 范围内死掉。从语义上讲,函数结束了。如果编译器决定它想要执行省略,那很好。问题是,exception object 不是你传递给throw 语句的东西,而是从它创建的temporary。他们的一生没有任何联系。顺便说一句,我都更新了。
  • @luk32:我们在兜圈子,因为 “异常对象不是您传递给 throw 语句的对象,而是由它创建的临时对象。它们的生命周期并不依赖于每个其他。” 做出与我已经评论过的相同的“两个不同的对象”假设。 “可能在捕获范围内死亡” 重复问题中的关注点;给定异常对象按值捕获的不必要的。无论如何,底线是我认为添加 15.1.3 会淡化答案的重点,但是根据您的评论,无论哪种方式都涵盖了我们。重新“我更新了两个 btw” - 我不知道你的意思。干杯
  • @MasihAkbari:列出了 C++ 标准草案的链接here。我相信我引用了 n3797。 ISO 出售正式、完整的标准文件。
【解决方案2】:

通过 const 引用捕获正是应该如何捕获异常。异常对象不一定存在于“堆栈上”。编译器负责使用适当的魔法来完成这项工作。

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

建议您酌情抛出std::runtime_errorstd::logic_error,或从中派生的类:

  • logic_error 当调用者请求超出您服务设计参数的内容时。
  • runtime_error 当来电者要求一些合理但外部因素阻止您兑现请求时。

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

【讨论】:

    【解决方案3】:

    来自except.throw

    抛出异常复制初始化(8.5、12.8)一个临时对象, 称为异常对象。临时值是一个左值,用于 初始化匹配处理程序(15.3)中声明的变量。如果 异常对象的类型将是不完整的类型或 指向除(可能是 cv 限定的)void 以外的不完整类型的指针 程序格式不正确。

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

    【讨论】:

      【解决方案4】:

      必须通过引用来捕捉,否则你不可能得到对象的正确动态类型。至于它的生命周期,标准保证,在[except.throw]

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

      【讨论】:

      • 好吧,您不必必须,不这样做是合法的。但是,如果您不这样做,那么您已经将枪对准了自己的膝盖。如果按值捕获,则引入可能的中间副本,更重要的是对象切片。
      猜你喜欢
      • 1970-01-01
      • 2011-05-03
      • 2019-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-08
      • 1970-01-01
      • 2013-02-06
      相关资源
      最近更新 更多