如果分配该数组引发异常,你应该释放一个数组吗?

问题描述 投票:17回答:3

我有一个别人写的潜在不稳定的类,我不得不创建该类的对象数组。我提到该类是不稳定的,因此它可能偶尔会在默认构造函数中抛出异常。我无权访问源代码,只能访问已编译的二进制文件。

当我使用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
 ___          __________________________________
| ---------->| :) | :) | :) | :) | :( | | | | | |
|___|        |____|____|____|____|____|_|_|_|_|_|
c++ exception-handling
3个回答
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'的构造从未完成,所以它的析构函数不会被触发。但是,成功构建的成员将被破坏(在上面的示例中没有演示,但如果你正在努力,这是一个有趣的练习)。

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


5
投票

在以下代码行中:

array = new Badclass[10];  

首先评估new Badclass[10]。如果抛出异常,则执行不会到达赋值。 array保留了之前的价值,即nullptr。它无法在nullptr上调用delete。

评论部分提出的问题:

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

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

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

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


2
投票

如果出现异常,则无需调用删除:

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

没有内存泄漏。

作为参考阅读有关new expression on cppreference

如果初始化通过抛出异常(例如从构造函数)终止,如果new-expression分配了任何存储,则调用适当的释放函数:operator delete for non-array type,operator delete [] for array type。

所以它清楚地说明delete[]是自动调用的,你不必调用它。

如果在抛出异常之前由new[]构造了部分对象,那么在释放内存之前,所有已构造的对象都将被破坏。这与包含数组的对象构造类似,并且在构造数组中的某个对象时抛出异常。

© www.soinside.com 2019 - 2024. All rights reserved.