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(...) {
std::abort;
}
}
Это резонный выбор, если программа не может продолжать работу после того, как в деструкторе произошла ошибка. Преимущество такого подхода – в предотвращении неопределенного поведения. Вызов abort упредит возникновение неопределенности.
• Перехватить исключение,
возбужденное вызовом close:DBConn::~DBConn
{
try {db.close;}
catch(...) {
}
}
Вообще говоря, такое «проглатывание» исключений – плохая идея, потому что мы теряем важную информацию:
Ни одно из этих решений не является идеальным. Проблема в том, что в обоих случаях программа не имеет возможности отреагировать на ситуацию, которая привела к возбуждению исключения внутри close.
Более разумная стратегия – спроектировать интерфейс DBConn так, чтобы его клиенты сами имели возможность реагировать на возникающие ошибки. Например, класс DBConn может предоставить собственную функцию close и таким образом дать клиентам шанс обработать исключение, возникшее в процессе операции. Объект этого класса мог бы отслеживать, было ли соединение DBConnection уже закрыто функцией close, и, если это не так, закрывать его в деструкторе. Тем самым предотвращается утечка соединений. Но если close все-таки будет вызвана из деструктора и возбудит исключение, то мы опять возвращаемся к описанным выше вариантам: прервать программу или «проглотить» исключение:
class DBConn {
public:
...
void close // новая функция для использования клиентом
{
db.close
closed = true;
}
~DBConn
{
if(!closed)
try {
db.close; // закрыть соединение, если этого не сделал
} // клиент
catch(...) { // если возникнет исключение, запротоколировать
// и прервать программу или «проглотить» его
}
}
private:
DBConnecton db;
bool closed;
};