Что стоит помнить при использовании исключений в конструкторе объекта?



Ответ: Если исключение не обработано, то c логической точки зрения разрушается объект, который еще не создан, а с технической, так как он еще не создан, то и деструктор этого объекта не будет вызван.



Пример:



class Base

{

private:

HANDLE m_hFile;



public:

Base()

{

std::cout << "Hello from Base()" << std::endl;

m_hFile = ::CreateFileA(...);

// Вызываем код, который в ходе своего выполнения бросает исключение

SomeLib.SomeFunc(...);

}



virtual ~Base()

{

std::cout << "Hello from ~Base()" << std::endl;

// Здесь мы планировали закрыть хэндл

::CloseHandle(m_hFile);

}

};



try

{

Base b;

}

catch(const std::exception &e)

{

std::cout << "Exception message: " << e.what() << std::endl;

}



Output:

Hello from Base()

Exception message: Something failed



Я немного модифицировал предыдущий пример, чтобы проблема была наглядней. Здесь объект m_hFile (если был открыт) утечет т.к. до CloseHandle() выполнение не дойдет. Т.е. имеем такие же проблемы как в первом примере: возможная утечка ресурсов или другие проблемы из-за нарушения логики работы класса.



Здесь могут спросить: «Как бы вы поступили при подобной ситуации». Правильный ответ: «Воспользовался бы умными указателями». Простой пример умного указателя:

class Base

{

private:

class CHandle

{

public:

~CHandle()

{

::CloseHandle(m_handle);

}

private:

HANDLE m_handle;

public:

// Для полноценного smart pointer'а перегрузки одной операции

// не достаточно, но для нашего примера и понимания вполне хватит

void operator = (const HANDLE &handle)

{

m_handle = handle;

}

};



CHandle m_hFile;



public:

Base()

{

std::cout << "Hello from Base()" << std::endl;

m_hFile = ::CreateFileA(...);

// Вызываем код, который в ходе своего выполнения бросает исключение

SomeLib.SomeFunc(...);

}



virtual ~Base()

{

std::cout << "Hello from ~Base()" << std::endl;

}

...



Теперь и без вызова деструктора Base хэндл будет закрыт, т.к. при уничтожении класса Base будет уничтожен объект m_hFile класса CHandle, в деструкторе которого и будет закрыт хэндл.



Изобретать велосипед, конечно, не надо, все уже написано до нас, это пример который можно написать на бумажке при соответствующем вопросе. А так есть boost, Loki, ATL и т.п., где это уже реализовано.



#cpp #programming



👉 @cpp_lib