Чтение онлайн

на главную

Жанры

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

Майерс Скотт

Шрифт:

Есть два основных способа избежать этой проблемы. Деструктор 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;

};

Перемещение вызова close из деструктора DBConn в код клиента (и

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

Что следует помнить

• Деструкторы никогда не должны возбуждать исключений. Если функция, вызываемая в деструкторе, может это сделать, то деструктор обязан перехватывать все исключения, а затем «проглатывать» их либо прерывать программу.

• Если клиенты класса нуждаются в возможности реагировать на исключения во время некоторой операции, то класс должен предоставить обычную функцию (то есть не деструктор), которая эту операцию выполнит.

Правило 9: Никогда не вызывайте виртуальные функции в конструкторе или деструкторе

Начну с повторения: вы не должны вызывать виртуальные функции во время работы конструкторов или деструкторов, потому что эти вызовы будут делать не то, что вы думаете, и результатами их работы вы будете недовольны. Если вы – программист на Java или C#, то обратите на это правило особое внимание, потому что это в этом отношении C++ ведет себя иначе.

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

class Transaction { // базовый класс для всех

public: // транзакций

Transaction;

virtual void logTransaction const = 0; // выполняет зависящую от типа

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

...

};

Transaction::Transaction // реализация конструктора

{ // базового класса

...

logTransaction;

}

class BuyTransaction: public Transaction { // производный класс

public:

virtual void logTransaction const = 0; // как протоколировать

// транзакции данного типа

...

};

class SellTransaction: public Transaction { // производный класс

public:

virtual void logTransaction const = 0; // как протоколировать

Поделиться:
Популярные книги

Империя отходит от края

Тамбовский Сергей
2. Империя у края
Фантастика:
альтернативная история
5.00
рейтинг книги
Империя отходит от края

Газлайтер. Том 8

Володин Григорий
8. История Телепата
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Газлайтер. Том 8

Возвышение Меркурия. Книга 8

Кронос Александр
8. Меркурий
Фантастика:
героическая фантастика
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия. Книга 8

Нищенка в элитной академии

Зимина Юлия
4. Академия юных сердец
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Нищенка в элитной академии

Последняя Арена

Греков Сергей
1. Последняя Арена
Фантастика:
боевая фантастика
постапокалипсис
рпг
6.20
рейтинг книги
Последняя Арена

Найденыш

Шмаков Алексей Семенович
2. Светлая Тьма
Фантастика:
юмористическое фэнтези
городское фэнтези
аниме
5.00
рейтинг книги
Найденыш

Энфис 7

Кронос Александр
7. Эрра
Фантастика:
героическая фантастика
рпг
аниме
5.00
рейтинг книги
Энфис 7

Мимик!

Северный Лис
1. Сбой Системы!
Фантастика:
боевая фантастика
5.40
рейтинг книги
Мимик!

Виконт. Книга 2. Обретение силы

Юллем Евгений
2. Псевдоним `Испанец`
Фантастика:
боевая фантастика
попаданцы
рпг
7.10
рейтинг книги
Виконт. Книга 2. Обретение силы

Повелитель механического легиона. Том I

Лисицин Евгений
1. Повелитель механического легиона
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Повелитель механического легиона. Том I

Волк: лихие 90-е

Киров Никита
1. Волков
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Волк: лихие 90-е

Смерть может танцевать 2

Вальтер Макс
2. Безликий
Фантастика:
героическая фантастика
альтернативная история
6.14
рейтинг книги
Смерть может танцевать 2

"Искажающие реальность" Компиляция. Книги 1-14

Атаманов Михаил Александрович
Искажающие реальность
Фантастика:
боевая фантастика
космическая фантастика
киберпанк
рпг
5.00
рейтинг книги
Искажающие реальность Компиляция. Книги 1-14

Идеальный мир для Лекаря 15

Сапфир Олег
15. Лекарь
Фантастика:
боевая фантастика
юмористическая фантастика
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 15