创建2D数组时出现异常:在C++中重新抛出之前进行清理

exception creating 2D Array: clean-up before re-throw in C++

本文关键字:新抛出 C++ 数组 2D 异常 创建      更新时间:2023-10-16

我想要一个函数,它可以动态创建并返回2D数组,或者当内存分配失败时,在清理已分配的行后,通过异常而不丢失信息

double **create (int rows, int cols)
{
    double **array = new double* [rows];
    for (int x=0; x<rows; x++)
    {
        try { array[x] = new double [cols]; }
        catch (exception &e)
        {
            x--;
            for (; x>=0; x--)
                delete[] array[x]; // clean up already allocated rows
            delete[] array;
            throw e; // pass exception
        }
        for (int y=0; y<cols; y++)
            array[x][y] = 0; // initialize array
    }
    return array;
}

所以我可以肯定,如果create抛出,就不会出现内存泄漏。但我能确定,传递的异常e是否与new抛出的directy"相同"而未被捕获吗?

例如

int main ()
{
    double **d;
    try { d = create (HUGE_x, HUGE_y); }
    catch (exception &e)
    {
        // 1. e could be thrown by new double* [rows]
        //      e.g. if HUGE_x is already to huge
        // 2. e could be thrown by throw e
        //      e.g. if after some rows no memory anymore
        // in both cases: is e the same?
    }
    return 0;
}

或者create函数中是否有必要包含catch (bad_alloc &e)?还是仅适用于catch (...) { /* do clean-up*/ throw; }?当重新抛出不是简单地使用throw;时,是否存在与C#中丢失堆栈跟踪相同的问题?

还有另一个更普遍的问题:

void f () { throw Object(); } // or throw "help";
void main ()
{
    try { f(); }
    catch (Object &e) // or catch (char *)
    {
        // Where is the Object or C-String created on the stack in function f()
        // since we aren't any more in function f() but we are dealing with
        // references/pointers to a non-existent stack?
    }
}

对于异常安全内存管理,请使用RAII。与其处理原始指针和异常处理程序,不如将资源分配给一个类,该类将在销毁时释放资源。这样,如果抛出异常,所有内容都会自动清理。

在这种情况下,std::vector是管理动态阵列的合适的RAII类:

vector<vector<double>> create (int rows, int cols) {
    return vector<vector<double>>(rows, vector<double>(cols));
}

(请注意,将2D数组表示为rows*cols大小的单个数组可能更有效,访问者可以为其提供2D索引。但这不是这个问题的主题,所以我不会详细介绍)

为了回答您的问题,尽管如果您编写异常安全代码,它们在很大程度上是不重要的:

但我能确定,传递的异常e是否与new抛出的directy"相同"而未被捕获吗?

不会;您正在抛出一个新对象,该对象是通过复制或移动e创建的,类型为exception

或者是否有必要在create函数中包含catch (bad_alloc &e)

这样就不会发现其他类型的异常。在这种情况下,这可能不是问题,但如果你要这样清理,你真的想捕获所有异常。重申一下:不要使用异常处理程序来清理。它非常容易出错。

还是仅适用于catch (...) { /* do clean-up*/ throw; }

这将重新考虑原始对象,这正是您想要的。(除了你一开始不想抓到任何东西)。

当重新抛出不是简单地使用throw;时,是否存在与C#中丢失堆栈跟踪相同的问题?

无论如何,标准异常都不会给您提供堆栈跟踪。如果您有一个带有堆栈跟踪的自定义异常类型,那么这取决于它是在复制/移动时生成新的异常类型,还是复制/移动现有的异常类型。

Object或C字符串在哪里?

异常处理机制会在某个地方创建它(而不是在即将展开的堆栈上),并在处理后销毁它。它没有具体说明它在哪里,只是它必须如何工作。

虽然这个问题现在已经很老了,并且已经得到了充分的回答,但我想添加一个关于重新思考异常的注释。在标准C++11中,您可以通过使用重新思考来保留原始异常信息

std::nested_exceptionstd::throw_with_nested


仅供参考:使用此功能,您还可以生成异常回溯,如StackOverflow中所述,无需调试器或繁琐的日志记录,只需编写一个适当的异常处理程序即可重新抛出嵌套的异常。

由于您可以对任何派生的异常类执行此操作,因此可以向这样的回溯添加大量信息!你也可以看看我在GitHub上的MWE,那里的回溯看起来像这样:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"