1

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

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;
}

所以我可以肯定,如果创建抛出,没有内存泄漏。但是我可以确定,传递的异常 e 与 new 直接抛出而不被捕获是“相同的”吗?

例如

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;
}

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

还有一个更普遍的问题:

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?
    }
}
4

2 回答 2

4

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

在这种情况下,std::vector是否有一个合适的 RAII 类来管理动态数组:

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

(请注意,将 2D 数组表示为 size 的单个数组可能更有效rows*cols,并使用访问器为其提供 2D 索引。但这与这个问题无关,所以我不会详细介绍)。

要回答您的问题,尽管如果您编写异常安全的代码,它们在很大程度上无关紧要:

但是我可以确定,传递的异常 e 与 new 直接抛出而不被捕获是“相同的”吗?

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

或者是否有必要catch (bad_alloc &e)在 create 函数内部?

那么您将不会捕获其他类型的异常。在这种情况下,这可能不是问题,但是如果您要像这样进行清理,您真的希望捕获所有异常。重申:不要使用异常处理程序进行清理。它非常容易出错。

或者它只适用于catch (...) { /* do clean-up*/ throw; }

这将重新抛出原始对象,这就是您想要的。(除了你不应该一开始就想抓住任何东西)。

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

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

对象或 C 字符串在哪里?

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

于 2013-11-22T16:39:58.623 回答
0

虽然这个问题现在已经很老了并且已经得到了充分的回答,但我想补充一点关于重新抛出异常的说明。在标准 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"
于 2019-12-24T15:44:57.800 回答