动态内存和构造函数异常

时间:2010-07-05 16:42:11

标签: c++ exception constructor

今天早些时候我发现了函数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的初始化会引发异常。
  4. 具体来说,我想知道至少:

    • {/ 1}}会导致/可能导致内存泄漏的内容。 我只想3个? (见here
    • 你能抓到new C(y)吗? 在案例1和2中是危险的吗?

    显然,我想最安全的做法是让delete b成为智能指针。但是暂时忽视这个选项,最好的做法是什么?

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

    这就意味着必须在catch中放置new以防其他东西抛出构造函数体?这样做是否存在安全问题(即,如果它是delete c本身会抛出的那些?)

3 个答案:

答案 0 :(得分:8)

尝试/捕捉功能块是不受欢迎的,就像goto一样 - 它们可能是一些有意义的角落情况,但最好避免它们:当一个对象无法构建时,你可以做的最好的事情失败并快速失败。

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

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

请注意,如果由于某些特定原因需要保持带有原始指针的指针,最好在初始化列表中将其初始化为0,然后在构造块内创建对象以避免此问题。还要记住,在类中直接保存多个(原始)资源会使确保没有泄漏资源变得非常困难 - 如果创建和分配了第一个资源,而第二个资源失败,则不会调用析构函数资源会泄漏。同样,如果你可以使用智能指针,那么你可以解决一个问题。

答案 1 :(得分:2)

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

  

完全构造的基类和   一个物体的成员应该是   在进入处理程序之前销毁   一个函数的尝试块   构造函数或析构函数   对象

  

当前处理的异常是   如果控制到达结束时重新生长   function-try-block的一个处理程序   构造函数或析构函数。   否则,函数返回时   控制到达处理程序的末尾   for function-try-block(6.6.3)。   流出一个结束   function-try-block相当于a   没有价值的回报;这导致了   在返回值中的未定义行为   功能

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

答案 2 :(得分:0)

要回答你的实际问题,任何时候在构造期间发生异常,执行就会在那里停止(从不构造正在构造的对象),控制传递给最近的异常处理程序(catch)。

仅在d导致异常时才会发生内存泄漏,在这种情况下,外部catch需要清理。

如果new C本身抛出,则永远不会构造该对象,因此您无需担心删除它。