【问题标题】:Dynamic memory and constructor exceptions动态内存和构造函数异常
【发布时间】:2011-03-11 23:55:12
【问题描述】:

今天早些时候,我发现了函数 try-catch 块(实际上来自 here),然后进行了一些研究——显然它们的主要用途是捕获构造函数初始化列表中抛出的异常。

无论如何,这让我想到了失败的构造函数,我已经到了一个我只需要一点澄清的阶段。这只是我试图更多地了解该语言,所以我没有实用示例,但这里有......


鉴于此示例代码:

class A
{
private:
    B b
    C *c;    //classes B, C & D omitted for brevity as not really relevant
    D d;
public
    A(int x, int y, int z)
};

A::A(int x, int y, int z)
try
    : b( x )
    , c( new C(y) )
    , d( z )
{
    //omitted
}
catch(...)
{
    //omitted
}

在这些情况下会发生什么:

  1. b 的初始化引发异常。
  2. c 的初始化引发异常。
  3. d 的初始化引发异常。

具体来说,我至少想知道:

  • 什么会/可能导致new C(y) 的内存泄漏。 我想只有 3 个? (见here
  • 你可以delete b 吗? 情况 1 和 2 有危险吗?

显然,我想最安全的做法是让c 成为智能指针。但暂时不考虑这个选项,最好的行动方案是什么?

在初始化程序中将c 设置为NULL,然后在构造函数主体中调用new 是否安全?

这意味着delete c 必须放在catch 中,以防在构造函数主体中抛出其他东西?这样做是否存在安全问题(即,如果是 c = new C(y); 本身抛出)?

【问题讨论】:

  • 您不能依赖将 c 设置为 NULL,因为如果 b 抛出异常,此时 c 将不会被设置为 NULL。正如你所说,使用智能指针。
  • @5ound - 我没想到的非常好点。

标签: c++ exception constructor


【解决方案1】:

Try/catch 功能块不受欢迎,就像 goto 一样——可能在某些极端情况下它们是有意义的,但最好避免它们:当一个对象构造失败时,你能做的最好的事情是失败和快速失败。

关于您的具体问题,当构造函数中抛出异常时,所有完全构造的子对象都将被销毁。这意味着在b 的情况下它将被销毁,在c 的情况下,它是一个原始指针,什么都不做。最简单的解决方案是将c 更改为处理分配内存的智能指针。这样,如果d 抛出,智能指针将被销毁并释放对象。这与 try/catch 块无关,而是与构造函数的工作方式有关。

一般来说,从 catch 块中删除指针也是不安全的,因为在执行初始化之前 不能保证指针的实际值。也就是说,如果bc 抛出,则c 可能不是0,但也不是有效指针,删除它将是未定义的行为。与往常一样,如果您保证bc 都不会抛出,并且假设bad_alloc 不是您通常从中恢复的东西,那么它可能是安全的。

请注意,如果出于某种特定原因需要保留带有原始指针的指针,最好在初始化列表中将其初始化为 0,然后在构造块内创建对象以避免此问题。还要记住,直接在一个类中保存多个(原始)资源使得很难确保没有资源泄漏——如果第一个资源被创建和分配,而第二个资源失败,析构函数将不会被调用,并且资源会泄露。同样,如果您可以使用智能指针,您将少处理一个问题。

【讨论】:

  • 是的,似乎我对这些的思考和阅读越多,它们似乎就越没有帮助。
  • 您的说法“try/catch 块不被接受,就像 goto...”完全是无稽之谈。 try/catch 是 语言特性,用于在异常上下文中编写具有适当错误处理的代码。您还期望如何处理异常?
  • @hkaiser:这里有一个误解,不知何故皱眉是函数级try/catch块:void f() try { ... } catch () {}。该功能已添加到语言中,并且相当未知,因此不推荐。我已经澄清了意图——或者至少尝试过——现在。常规的 try/catch 块 inside 函数,正如您正确指出的那样,是用于错误处理的 语言功能。当我回到家时,我会尝试从众所周知的来源中找到参考资料。这是第一个:GOTW#66
  • @David - 您评论中的链接非常有用 - 这是“道德”部分(3 到 4 之间)的引述,我认为它提供了答案:“......或新[] 它在构造函数主体中,可以使用本地 try-block 或其他方式安全地清理它。”对我来说,这听起来像是“最佳”解决方案(不包括智能指针)——在构造函数主体中执行每个所需的new,在它自己的、嵌套的、try/catch 中——然后捕获可以清理所有以前的工作,然后抛出一些东西。
  • @DMA57361:“最佳”解决方案是对每个资源使用智能管理器:新对象的智能指针,动态分配的数组的 std::vector 等...使用 try/ catch 处理资源的释放类似于使用锤子进行手术,它可能会起作用,但它不必要地复杂并且不太可能工作那么长时间(维护......)
【解决方案2】:

你不能在 function-try-block 处理程序中做任何事情,除了将一个异常转换为另一个异常。您无法阻止抛出异常。你不能对班员做任何事。所以不,你不能在构造函数的函数try-block中做delete c

完全构造的基类和 对象的成员应为 在进入处理程序之前销毁 的功能尝试块 构造函数或析构函数 对象。

当前处理的异常是 如果控制到达终点则重新抛出 函数try块的处理程序 构造函数或析构函数。 否则,函数在何时返回 控制到达处理程序的末尾 对于功能尝试块(6.6.3)。 从尾端流出 function-try-block 相当于一个 无价值返回;这导致 值返回中的未定义行为 功能。

(来自 C++ 标准的 15.3 [except.handle])

【讨论】:

  • 因此,基本上,对象被销毁(有效地)作为在catch 开始之前发生的展开的一部分。因此,在catch 中,“当前”对象不存在;您不能尝试清理不存在的对象。
  • @DMA57361 - 是的。我的理解是 function-try-block 被添加到语言中,仅用于构造函数中的异常翻译,以促进独立库中类的联合使用。此外,您可以将日志记录放在那里。差不多就是这样……
【解决方案3】:

要回答您的实际问题,只要在构造过程中发生异常,就会立即停止执行(正在构造的对象永远不会被构造)并将控制权传递给最近的异常处理程序 (catch)。

你说得对,只有当d 导致异常时才会发生内存泄漏,在这种情况下,外部catch 需要清理。

如果new C 本身抛出,对象永远不会被构造,所以你不必担心删除它。

【讨论】:

  • 关于如何安全清理渔获物有什么建议吗?我感觉它做不到,你可能会遇到内存泄漏或删除未初始化指针的风险。
  • 在某种程度上,你是对的 - 它会泄露给尴尬的情况,你不能总是保证一个干净的解决方案。如果bcd 都抛出不同的异常,您可以找出故障发生的确切位置并相应地进行清理。
猜你喜欢
  • 2019-08-19
  • 2014-01-05
  • 1970-01-01
  • 2017-06-25
  • 2019-10-12
  • 1970-01-01
  • 2010-12-01
  • 2018-11-18
  • 2011-06-11
相关资源
最近更新 更多