如果分配该数组引发异常,是否应该释放该数组?

时间:2018-10-16 06:51:40

标签: c++ exception-handling

我有一个别人写的可能不稳定的类,并且我必须创建该类的对象数组。我提到类是不稳定的,因此它有时可能会在默认构造函数中引发异常。我无权访问源代码,只有编译的二进制文件。

当我使用new分配这些类型的对象的动态数组时,这些坏对象之一可能会引发异常。它引发了一个自定义异常,而不是std::bad_alloc。 无论如何,我需要使程序从异常中恢复并继续进行调整,尽管设置了一些错误标志,但没有设置。我认为我应该delete与数组关联的内存,以防止内存泄漏。

我的推理是,如果类抛出异常以在数组中间的某个位置构造一个元素,则该元素将无法正确构造,并且所有将来的元素都将被该异常停止构造,但是前面的元素自从在引发异常之前发生那件事以来,它已经被正确构造。我想知道,在delete中调用catch (...) { }是个好主意吗?我将如何解决此内存泄漏?

Badclass* array = nullptr;

try {
  array = new Badclass[10];  // May throw exceptions!
} catch (...) {
  delete[] array;
  array = nullptr;
  // set error flags
}

这是我在内存中可视化它的方式。这是正确的吗?

 array         0    1    2    3   4    5 6 7 8 9
 ___          __________________________________
| ---------->| :) | :) | :) | :) | :( | | | | | |
|___|        |____|____|____|____|____|_|_|_|_|_|

3 个答案:

答案 0 :(得分:25)

回答最后一个问题:

  

我将如何解决此内存泄漏?

没有内存泄漏。仅当BadClass本身动态分配内容并且从未在其析构函数中释放内容时,才会发生泄漏。由于我们忽略了您的BadClass实施方式,而不是进行猜测,因此这取决于您。 new BadClass[N];本身泄漏内存的唯一方法是,如果内存泄漏完成,您稍后会抛弃您正在手动管理的唯一引用(array)。

一个动态分配的数组,在其中的元素的构造函数之一中抛出,将(a)以相反的顺序针对已构造的元素撤消析构函数,(b)释放分配的内存,最后(c)主持实际的扔到最近的catch处理程序(如果没有,则使用默认处理程序)。

由于发生了抛出,因此对结果数组指针的赋值永远不会发生,因此不需要delete[]

最佳示例展示:

#include <iostream>

struct A 
{
    static int count;
    int n;

    A() : n(++count)
    {
        std::cout << "constructing " << n << '\n';
        if (count >= 5)
            throw std::runtime_error("oops");
    }

    ~A()
    {
        std::cout << "destroying " << n << '\n';
    }
};

int A::count;

int main()
{
    A *ar = nullptr;
    try
    {
        ar = new A[10];
    }
    catch(std::exception const& ex)
    {
        std::cerr << ex.what() << '\n';
    }
}

输出

constructing 1
constructing 2
constructing 3
constructing 4
constructing 5
destroying 4
destroying 3
destroying 2
destroying 1
oops

请注意,由于元素'5'的构造从未完成,因此不会触发其析构函数。但是,成功构建 的成员被破坏了(在上面的示例中没有演示,但是如果愿意的话,这是一个有趣的练习)。

所有这些,无论如何都使用智能指针。

答案 1 :(得分:5)

在下面的代码行中:

array = new Badclass[10];  

new Badclass[10]首先被评估。如果抛出异常,则执行不会达到分配。 array保留其先前的值nullptr。 在nullptr上调用delete无效。

评论部分的问题:

  

这种行为是否基于与堆栈展开相同的原理?

标准中有关“异常处理”的部分可帮助我们了解分配时抛出异常时会发生什么情况。

  

18异常处理[例外]
  ...
   18.2构造函数和析构函数[except.ctor]

     

1。当控制权从抛出异常的点传递到处理程序时,析构函数由进程调用,该进程在本小节中称为<堆栈>展开。
  ...
  3如果通过委托构造函数以外的对象的初始化或销毁被异常终止,则为该对象的每个直接子对象以及为完整对象的虚拟基类子对象调用析构函数,这些对象的初始化已完成且其初始化析构函数尚未开始执行,只是在销毁的情况下,不会破坏类联合类的变体成员。子对象以其完成构建的相反顺序被破坏。在进入构造函数或析构函数(如果有)的function-try-block的处理程序之前,对这种破坏进行排序。

答案 2 :(得分:2)

如果出现以下情况,则无需调用delete

array = new Badclass[10];  // May throw exceptions!

没有内存泄漏。

作为参考,请阅读有关new expression on cppreference的信息:

  

如果初始化因引发异常而终止(例如,   构造函数),如果new-expression分配了任何存储空间,则它调用   适当的释放函数:删除非数组类型的运算符,   运算符删除[]以获取数组类型

因此,它明确指出delete[]是自动调用的,您不必调用它。

如果在抛出异常之前由new[]构造了其中的部分对象,则所有构造的对象将在释放内存之前被销毁。就像包含数组的对象构造一样,在数组中构造某些对象时会引发异常。