Читаем Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ полностью

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 завершился неудачно;

}

}


Вообще говоря, такое «проглатывание» исключений – плохая идея, потому что мы теряем важную информацию: что-то не сработало ! Но иногда лучше поступить так, чтобы избежать преждевременной остановки программы или неопределенного поведения. Выбирать этот подход следует лишь в случае, когда программа в состоянии надежно продолжать исполнение, даже после того, как ошибка произошла, но была проигнорирована.

Ни одно из этих решений не является идеальным. Проблема в том, что в обоих случаях программа не имеет возможности отреагировать на ситуацию, которая привела к возбуждению исключения внутри close.

Более разумная стратегия – спроектировать интерфейс DBConn так, чтобы его клиенты сами имели возможность реагировать на возникающие ошибки. Например, класс DBConn может предоставить собственную функцию close и таким образом дать клиентам шанс обработать исключение, возникшее в процессе операции. Объект этого класса мог бы отслеживать, было ли соединение DBConnection уже закрыто функцией close, и, если это не так, закрывать его в деструкторе. Тем самым предотвращается утечка соединений. Но если close все-таки будет вызвана из деструктора и возбудит исключение, то мы опять возвращаемся к описанным выше вариантам: прервать программу или «проглотить» исключение:


class DBConn {

public:

...

void close // новая функция для использования клиентом

{

db.close

closed = true;

}

~DBConn

{

if(!closed)

try {

db.close; // закрыть соединение, если этого не сделал

} // клиент

catch(...) { // если возникнет исключение, запротоколировать

записать в протокол,

// и прервать программу или «проглотить» его

что вызов close

завершился неудачно;

}

}

private:

DBConnecton db;

bool closed;

};


Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже