00:14 

Трюк 0x0004 — Исключение в конструкторе не смертельно

Куб 0
У многих есть мнение, что ресурсы, выделенные в потерпевшем крах конструкторе, очень трудно освободить, так как деструктор для объекта с незавершенным конструктором не вызывается, а указатели на уже инициализированные ресурсы теряются, что приводит к утечке памяти и прочим неприятностям.
А что, если использовать не обычные указатели, а "умные" указатели? Например, стандартный std::auto_ptr? Если память для ресурсов организовать с помощью std::auto_ptr, то проблема освобождения ее снимается с плеч разработчика.
Возьмем для примера два класса, Loser и Fixxer. Первый, в полном соответствии с названием, в конструкторе выделяет ресурс (память) и при передаче неподходящего параметра вылетает с исключением. Второй, так же в соответствии со своим названием, пытается выправить ситуацию. Посмотрим, как он с этим справляется.

//------------------------------------------------------------
#include <memory>

class Loser {
public:
Loser(int Number): m_Number(Number) {
::printf("Loser::Loser(), Number=%i\n", m_Number);
// выделение памяти
m_Buffer=new char[50000000];
// если параметр меньше нуля, то класс не продолжает работу
if (Number<0) {
// освобождение памяти
Cleanup();
// выбрасывается исключение
throw m_Number;
}
}
~Loser() {
// освобождение памяти
Cleanup();
::printf("Loser::~Loser(), Number=%i\n", m_Number);
}
private:
int m_Number;
char *m_Buffer;
void Cleanup() {
::printf("Loser::Cleanup(), Number=%i\n", m_Number);
delete [] m_Buffer;
}
};

class Fixxer {
public:
Fixxer(Loser *Loser1=0, Loser *Loser2=0):
m_Loser1(Loser1 ? Loser1 : new Loser(1)),
m_Loser2(Loser2 ? Loser2 : new Loser(-1)) {
::printf("Fixxer::Fixxer()\n");
}
~Fixxer() {
::printf("Fixxer::~Fixxer()\n");
}
private:
// используем "умные" указатели
const std::auto_ptr<Loser> m_Loser1, m_Loser2;
};
//------------------------------------------------------------
int main() {
try {
Fixxer();
}
catch (int &Number) {
::printf("catch(int), Number=%i\n", Number);
}
return 0;
}
//------------------------------------------------------------


Если использовать обычные указатели, то в деструкторе Fixxer::~Fixxer() потребовался бы вызов delete m_Loser1 и delete m_Loser2 для вызова их деструкторов. Но ведь если исключение произошло в конструкторе Fixxer::Fixxer(), то объект не сконструирован, и для него никогда не будет вызван деструктор.
Посмотрим на распечатку консольного вывода. Результат работы этой программы вполне удовлетворителен, хотя деструктор Fixxer::~Fixxer() и не вызывается.

//------------------------------------------------------------
Loser::Loser(), Number=1
Loser::Loser(), Number=-1
Loser::Cleanup(), Number=-1
Loser::Cleanup(), Number=1
Loser::~Loser(), Number=1

catch(int), Number=-1
//------------------------------------------------------------
Мы видим, что сначала вызываются оба конструктора Loser::Loser(). В конструкторе второго объекта происходит проверка условия, вызывается Cleanup() и выбрасывается исключение.
А теперь посмотрим на строчки, выделенные курсивом. Видно, что для первого объекта вызывается Cleanup() ... из деструктора! Таким образом, использование std::auto_ptr позволяет выполнять корректное освобождение ресурса, инициализированного до возникновения исключения в конструкторе.

@темы: Исключения

URL
   

Записная книжка программиста C++

главная