Страницы

Поиск по вопросам

четверг, 2 января 2020 г.

Как сообщить вызывающей стороне об ошибке в деструкторе

#cpp #исключения #деструктор


Какие существуют способы сообщить о проблеме, возникшей в деструкторе?

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

Например, не удается закрыть файл (или группу файлов), и простое игнорирование этой
ситуации нарушит согласованность огромной базы данных. 

Я и не представлял раньше, насколько серьезна эта проблема. Во всех без исключения
книгах, с которыми я имел дело, предполагалось, что если в деструкторе и возникают
какие-то проблемы, то они либо решаются там же, либо не решаются вообще. 

Как решается эта проблема?
    


Ответы

Ответ 1



Ответ на этот вопрос есть в C++ super-FAQ. Приведу основные выкладки оттуда и добавлю кое-что от себя. Если в деструкторе произошла аварийная ситуация, можно делать всё что угодно (писать в лог, завершить процесс...), но ни в коем случае не выкидывать исключений наружу деструктора. В противном случае процесс всё равно будет завершён через std::terminate, если во время разворачивания стека из-за одного исключения будет вызвано (и не обработано) новое. В современном C++ любой деструктор уже неявно помечается noexcept, а это значит, что попытка выкинуть из него исключение приведёт к завершению процесса через std::terminate, даже если вызывающий код имеет подходящий try-catch. Чтобы тем не менее иметь возможность выкинуть исключение из деструктора, нужно явно указать noexcept(false): struct S { ~S() noexcept(false) { throw 42; } }; Если иметь возможность проверять наличие исключения (как предложил @Ant в комментарии это можно сделать путём сравнения значений, возвращаемых std::uncaught_exceptions (с++17) в конструкторе и деструкторе объекта), то можно безопасно выбрасывать исключение из деструктора пока ещё нет другого активного исключения, не имеющего своего обработчика. Но в таком случае нужно всё равно как-то обработать ситуацию, если ошибка в деструкторе возникла при наличии такого исключения. Поэтому наиболее простой вариант - просто не выкидывать исключения, а сразу делать что-то другое. Нормальная практика - помещать операции, приводящие к исключениям, в отдельные функции и вызывать их отдельно от деструктора. Код в деструкторе должен быть максимально простым и не требовать сложной обработки, которая может породить исключение. Например, если вы работаете с объектом типа std::fstream, который закрывает файл в деструкторе, но при этом хотите иметь какую-то обработку проблемы закрытия файла, то вызовите std::fstream::close отдельно и проверьте наличие failbit для выявления проблем. Обработайте это как душе угодно (с исключениями или без), а на последующий вызов деструктора std::fstream вам уже не нужно обращать никакого внимания. Здесь стоит заметить, что у std::fstream (через базовый класс std::basic_ios) есть возможность установить битовую маску для генерации исключений с помощью функции exceptions. Если в этой маске будет присутствовать failbit, то деструктор может выкинуть исключение при ошибке закрытия файла (и это приведёт к завершению программы), но если это закрытие, т.е. close(), вызвать самостоятельно, то исключение вполне можно обработать стандартным способом.

Ответ 2



Допустим ошибка в деструкторе является причиной неуспешного завершения программы. И если я заранее имею сомнения по этому поводу, то могу в конструкторе изменить функциональность завершения. Например: struct A { std::terminate_handler th; int i = 5; A() { th = std::set_terminate([](){ std::cerr << "error in class destructor"; }); } ~A() { if (i != 5) std::terminate(); std::set_terminate(th);} }; И программа: A a; a.i = 0; Выдаст сообшение перед завершением. Вопрос не включает в себя конкретного примера, поэтому трудно угадать что вам нужно конкретно...

Комментариев нет:

Отправить комментарий