Исключение при создании 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"