Исключение при создании 2D Array: очистка перед повторным выбросом в C++

Я хочу иметь функцию, которая динамически создает и возвращает 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;
}

Так что я могу быть уверен, что при создании бросков утечки памяти нет. Но могу ли я быть уверен, что переданное исключение e является "тем же самым", как если бы оно было напрямую брошено новым и не поймано?

Например

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

1 ответ

Решение

Для исключительного безопасного управления памятью используйте 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 является "тем же самым", как если бы оно было напрямую брошено новым и не поймано?

Не будет; вы бросаете новый объект, созданный копированием или перемещением e с типом exception,

Или нужно иметь catch (bad_alloc &e) внутри функции создания?

Тогда вы не поймаете другие типы исключений. В этом случае это может не быть проблемой, но вы действительно хотите перехватить все исключения, если вы собираетесь убирать вот так. Повторим: не используйте обработчики исключений для очистки. Это очень подвержено ошибкам.

Или это работает только с catch (...) { /* do clean-up*/ throw; }?

Это отбросит исходный объект, то есть то, что вы хотите. (За исключением того, что вы не должны хотеть ловить что-либо в первую очередь).

Есть ли та же проблема, что и в C# с потерей трассировки стека, когда повторный бросок не с throw; просто?

Стандартные исключения в любом случае не дают вам трассировки стека. Если у вас есть пользовательский тип исключения со трассировкой стека, то это зависит от того, генерирует ли он новый при копировании / перемещении или копирует / перемещает существующий.

Где находится объект или C-String?

Механизм обработки исключений создает его где-то (не в стеке, который собирается разматываться) и уничтожает его после обработки. Точно не указано, где оно находится, просто как оно должно работать.

Хотя этот вопрос уже довольно старый и на него дан адекватный ответ, я хотел бы добавить примечание о повторном генерировании исключений. В стандартном C++11 вы можете сохранить исходную информацию об исключении, повторно выбросив:

std::nested_exception а также std::throw_with_nested


К вашему сведению: используя это, вы также можете генерировать обратные трассировки исключений, как описано в Stackru здесь и здесь, без необходимости в отладчике или громоздком журналировании, просто написав правильный обработчик исключений, который будет повторно генерировать вложенные исключения.

Так как вы можете сделать это с любым производным классом исключений, вы можете добавить много информации в такую ​​трассировку! Вы также можете взглянуть на мою MWE на GitHub, где трассировка будет выглядеть примерно так:

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"
Другие вопросы по тегам