Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ | страница 43
Это достаточно просто понять. Но что вы должны делать, если в вашем деструкторе необходимо выполнить операцию, которая может породить исключение? Например, предположим, что мы имеем дело с классом, описывающим подключение к базе данных:
>class DBConnection {
>public:
>...
>static DBConnection create(); // функция возвращает объект
>// DBConnection; параметры для
>// простоты опущены
>void close(); // закрыть соединение; при неудаче
>}; // возбуждает исключение
Для гарантии того, что клиент не забудет вызвать close для объектов DBConnection, резонно создать класс для управления ресурсами DBConnection, который вызывает close в своем деструкторе. Классы, управляющие ресурсами, мы подробно рассмотрим в главе 3, а здесь достаточно прикинуть, как должен выглядеть деструктор такого класса:
>class DBConn { // Класс для управления объектами
>public: // DBConnection
>...
>~DBConn() // обеспечить, чтобы соединения с базой
>{ // данных всегда закрывались
>db.close();
>}
>private:
>DBConnecton db;
>};
Тогда клиент может содержать такой код:
>{ // блок открывается
>DBConn dbc(DBConnection::create()); // создать объект DBConnection
>// и передать его объекту DBConn
>... // использовать объект DBConnection
>// через интерфейс DBConn
>} // в конце блока объект DBConn
>// уничтожается, при этом
>// автоматически вызывается метод close
>// объекта DBConnection
Все это приемлемо до тех пор, пока метод close завершается успешно, но если его вызов возбуждает исключение, то оно покидает пределы деструктора DBConn. Это очень плохо, потому что деструкторы, возбуждающие исключения, могут стать источниками ошибок.
Есть два основных способа избежать этой проблемы. Деструктор DBConn может:
• Прервать программу, если close возбуждает исключение; обычно для этого вызывается функция abort:
>DBConn::~DBConn()
>{
>try {db.close();}
>catch(...) {
>записать в протокол, что вызов close завершился неудачно;
>std::abort();
>}
>}
Это резонный выбор, если программа не может продолжать работу после того, как в деструкторе произошла ошибка. Преимущество такого подхода – в предотвращении неопределенного поведения. Вызов abort упредит возникновение неопределенности.
• Перехватить исключение, возбужденное вызовом close:
>DBConn::~DBConn()
>{
>try {db.close();}
>catch(...) {
>записать в протокол, что вызов close завершился неудачно;
>}
>}
Вообще говоря, такое «проглатывание» исключений – плохая идея, потому что мы теряем важную информацию: что-то не сработало