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