c++exceptionstack-tracemultidimensional-arrayrethrow

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


I want to have a function which dynamically creates and returns a 2D array or when memory allocation fails passes the exception without information loss after cleaning up already allocated rows:

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

So I can be sure, if create throws, there is no memory leak. But can I be sure, that the passed exception e is "the same" as if directy thrown by new and not catched?

E.g.

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

Or is it necessary to have catch (bad_alloc &e) inside the create function? Or does it work only with catch (...) { /* do clean-up*/ throw; }? Is there the same problem as in C# with losing stack trace when re-throw is not with throw; simply?

And another, more general question:

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

Solution

  • For exception-safe memory management, use RAII. Rather than juggling raw pointers and exception handlers, assign the resource to a class which will release it on destruction. That way, everything is cleaned up automatically if an exception is thrown.

    In this case, std::vector is a suitable RAII class managing a dynamic array:

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

    (Note that it may be more efficient to represent the 2D array as a single array of size rows*cols, with accessors to provide 2D indexing into it. But that's off-topic for this question, so I won't go into tedious detail).

    To answer your questions, although they're largely irrelevent if you write exception-safe code:

    But can I be sure, that the passed exception e is "the same" as if directy thrown by new and not catched?

    It won't be; you're throwing a new object, created by copying or moving e, with type exception.

    Or is it necessary to have catch (bad_alloc &e) inside the create function?

    Then you won't catch other types of exceptions. In this case, that might not a problem, but you really want to be catching all exceptions if you're going to be cleaning up like this. To reiterate: don't use exception handlers to clean up. It's very error-prone.

    Or does it work only with catch (...) { /* do clean-up*/ throw; }?

    That will rethrow the original object, which is what you want. (Except that you shouldn't want to be catching anything in the first place).

    Is there the same problem as in C# with losing stack trace when re-throw is not with throw; simply?

    Standard exceptions don't give you a stack trace anyway. If you have a custom exception type with a stack trace, then it depends on whether it generates a new one when copied/moved, or copies/moves the existing one.

    Where is the Object or C-String?

    The exception handling mechanism creates it somewhere (not on the stack which is about to be unwound), and destroys it once it's been handled. It's not specified exactly where it is, just how it must work.