Здравствуйте, Николай Ивченков, Вы писали:
НИ>Юрий Жмеренецкий:
ЮЖ>>Может быть передан == стратегия известна.
НИ>Общая стратегия — да, известна. Конкретные же действия, которые нужно выполнить при обработке исключительной ситуации, могут задаваться вызывающим кодом (т.е. они не будет жёстко зашиты в деструктор).
Вот, потихоньку приходим к отвязыванию конкретных действий от состояния. С высокой долей вероятности может возникнуть необходимость использования разных стратегий при выполнении одной и той же операции и придется делать что-то вроде такого:
/*virtual*/ void X::set_error_handler(function<void()>)
ЮЖ>>С TLS еще больше вопросов.
НИ>В случае с TLS стратегию обработки ошибок можно вообще не хранить в объекте, а доставать прямо из хранилища, выделенного для данного потока. Помещать стратегию в конец хранилища и удалять её оттуда будет одна из вызывающих функций.
И в результате получится вручную поддерживаемый стек активных обработчиков. Такая схема может быть жизнеспособной в некоторых специфических случаях, но она никак не выглядит решением для обработки исключений именно в деструкторах (хотя решений как таковых здесь нет, см. ниже).
НИ>В случае принятия решения о размещении небессбойного кода в деструкторе выбор невелик.
НИ>Хотя веских причин для принятия такого решения я не знаю
Я в первую очередь говорю о случаях, когда можно исключить необходимость обработки сбойных случаев в деструкторе. Звучит противоречиво, объясню:
Необходимо наличие нескольких стратегий обработки, которые можно разделить как минимум на два класса эквивалентности: В первом клиент готов обработать исключение и у него есть своя стратегия обработки. Отсутствие такой обработки рассматривается как ошибка в программе. Основная задача инициировать вызов, и в случае ошибки — предпринять какие-либо действия для выхода из состояния, в которое переходит программа после вызова некоторой функции(ий) и которое классифицируется клиентом как ошибочное. Второй класс — игнорирование результата с одновременной гарантией вызова. В этом случае, по классификации клиента, любой сбой не является ошибкой.
Приблизительно вот такой код можно использовать в обоих озвученных стратегиях:
void X::close()
{
assert(!closed);
if(!api::close(...))
throw some_exception();
assert(closed);
}
X::~X()
{
if(!closed)
api::close(...);
}
Здесь возможная проблема — забытый вызов close для первой стратегии. Но, во-первых "отсутствие такой обработки рассматривается как ошибка в программе", во-вторых — при наличии сформулированной задачи, это не проблема(имхо).
Обработка ошибок в деструкторе здесь не нужна
по определению (двух вышеописанных стратегий). Если api::close кидает исключения — их просто необходимо игнорировать. Так же как и игнорировать любые возвращаемые результаты.
ЮЖ>>Заодно придется определить семантику хранения членов контекста. Это особенно актуально, если стратегии можно изменять/сохранять.
НИ>При использовании TLS достаточно обычных ссылок — обработка исключения будет осуществлена в том потоке, который задал стратегию обработки.
Ссылок может и достаточно, но семантику определить все же придется, иначе ссылки могут быть мертвыми.
ЮЖ>>Все что ты описываешь, можно с легкостью использовать при выносе в отдельный метод с формированием явной инфраструктуры.
НИ>Скорее всего, можно, но на 100% я не уверен (если я правильно понял, имеется в виду удаление всего небессбойного кода из деструктора и размещение его в специальном методе).
В Boost.IOStreams есть концепции closable/flushable — это почти то, о чем я говорю.