Здравствуйте, jahr, Вы писали:
J>Много лет не могу для себя решить, как правильно себя вести при возникновении каких-то проблем во время выполнения деструктора.
Конструктор, предназначен для инициализации полей, деструктор, для освобождения ресурсов.
Никаких тяжёлых операций, которые могут пройти неудачно, в них быть не должно. С++ не всесилен.
При необходимости производить тяжёлые операции при инициализации и деинициализации, нужно
делать всё по старинке как в Cи, разруливать в специализированных функциях: object.initialize(), object.deinitialize()
Здравствуйте, jahr, Вы писали:
J>Про конструктор, кстати, не совсем понятно — почему не проводить там тяжелых операций если следить за тем, чтобы выполнение корректно завершилось при возникновении исключения посередине конструктора, basic exception safety вроде как для этого достаточно?
Если полноценная инициализация происходит только в случае успешного завершения тяжёлой операции,
например, открытие IO дескриптора, то неудача в этой операции обеспечит "неполноценность" объекта,
и после его создания эту полноценность всё равно придётся проверять чем-то вроде object.is_good().
Так можно вместо is_good() вызывать initialize() и смотреть возврат.
J>Если каждому объекту писать init/deinit кроме конструктора-деструктора, то все это не будет отличаться от С, по большому счету, мне кажется это очень сильно обесценивает все эти С++-навороты.( Лучше уж тогда ошибки игнорировать в деструкторе.(
С++ не всесилен, несмотря на наличие ++ во основном он недалеко от Си оторвался.
ЗЫ С++ навороты вовсе не в конструкторах/деструкторах.
Здравствуйте, jahr, Вы писали:
J>В общем, буду признателен, если вы поделитесь своими способами реализовать описанный в примере сценарий работы идеологически правильным способом.)
Обычно вручную расставляют .close() прям перед разрушением:
{
File a,b;
// ... - may throw
b.close(); // may throw
a.close(); // may throw
}
{
File a,b;
scope(success)
{
b.close(); // may throw
a.close(); // may throw
};
// ... - may throw
}
В этом случае не надо расставлять .close() в каждой точке выхода — return, break, etc — достаточно один раз и рядом с объектом.
Экспериментальная альтернатива — двухфазная семантика разрушения. Вместо деструктора пишутся два метода release и deferred — release выполняется всегда в самом конце, deferred выполняется перед release и только в случае если не летит исключение. В итоге пример выше переписывается в:
Здравствуйте, smeeld, Вы писали:
S>ЗЫ С++ навороты вовсе не в конструкторах/деструкторах.
Многи люди (и я в том числе) считают, что деструкторы — одна из главных фич C++. Где-то (на StackOverflow?) был вопрос про самый красивый, мощный и выразительный однострочник на C++, и в качестве образца приводился вот этот код:
Здравствуйте, jahr, Вы писали:
S>>Конструктор, предназначен для инициализации полей, деструктор, для освобождения ресурсов. S>>Никаких тяжёлых операций, которые могут пройти неудачно, в них быть не должно. С++ не всесилен. S>>При необходимости производить тяжёлые операции при инициализации и деинициализации, нужно S>>делать всё по старинке как в Cи, разруливать в специализированных функциях: object.initialize(), object.deinitialize() J>Очень жаль, а ведь счастье было так близко.)
Тяжёлые операции в конструкторе — без проблем.
Тяжёлые операции в деструкторе — можно, но нужно следить за исключениями.
Здравствуйте, jahr, Вы писали:
L>>Деструктор вызывается при разрушении объекта. То есть, что бы не случилось в Вега деструкторе, остается в деструкторе. И вызываемый код doesn't care. L>>Отсюда и плясать.
J>Как-то это неправильно получается — язык by-design подразумевает наличие каких-то "черных дыр" кода, это ж почти undefined behaviour.)
Нет тут никакой черной дыры. Деструктор вызывается при разрушении объекта. Объект разрушается, когда вызываемый код решает "все, я с тобой закончил, ты мне больше не нужен". Это подразумевает, что все, что вызываемому коду нужно было от объекта, он уже получил.
А объект, в свою очередь, способен гарантированно подтереть за собой в деструкторе.
Это же очевидно.
L>>"Запись в файл" — это не "освобождение ресурса". Это и правда весьма сложное действие, которое может обломаться. В зависимости от стратегии обработки ошбиок, внешней логике на это может быть пофигу, тогда в принципе это можно и в деструкторе делать. L>>А может и не пофигу, тогда подобному действию не место в деструкторе.
J>Ну, можно найти пример и не так явно сложный, например, — многие winapi-шные функции освобождения ресурсов имеют код возврата,
Это зависит от нескольких вещей. Во-первых, важен ли нам код возврата. Во-вторых, можем ли мы что-то сделать в случае, если код ошибки возвращает ошибку. В-третьих, это скорее зависит от стратегии обработки ошибок в программе.
Все эти вопросы прямого отношения к деструкторам не имеют.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Деструкторы могли бы нормально кидать исключения, например если бы многократные исключения аккумулировались в одно. EP>В случаях же где из-за алгоритмических особенностей нужен не кидающий деструткор — использовали бы noexcept.
EP>Даже была статья на эту тему — "Destructors That Throw: Evil, or Just Misunderstood? — Jon Kalb and Dave Abrahams" (оригинальный сайт выпилен, а копию/кэш я сходу не нашёл). Там была фраза, что-то в духе: "делать std::terminate в таких случаях это слишком драконовские меры".
Почитал. Принципиальной невозможности аккумулировать исключения имхо особенно нет. Но у меня другой вопрос — а что с объектом-то? Вот выкинул он исключение на полпути деструктора. И стал зомби. Вернуть к жизни его нельзя. Добить? Как? Разрешать рекуррентный вызов деструктора, чтобы наверняка добить? Так это ничем не отличается от цепочки cleanup() — delete.
Или принять ограничения, что исключение в деструкторе — чисто информационное и объект, выбросивший его, гаратированно убит?
Здравствуйте, landerhigh, Вы писали:
L>>>Деструктор вызывается при разрушении объекта. То есть, что бы не случилось в Вега деструкторе, остается в деструкторе. И вызываемый код doesn't care. L>>>Отсюда и плясать. J>>Как-то это неправильно получается — язык by-design подразумевает наличие каких-то "черных дыр" кода, это ж почти undefined behaviour.) L>Нет тут никакой черной дыры. Деструктор вызывается при разрушении объекта. Объект разрушается, когда вызываемый код решает "все, я с тобой закончил, ты мне больше не нужен". Это подразумевает, что все, что вызываемому коду нужно было от объекта, он уже получил. L>А объект, в свою очередь, способен гарантированно подтереть за собой в деструкторе. L>Это же очевидно.
Деструкторы могли бы нормально кидать исключения, например если бы многократные исключения аккумулировались в одно.
В случаях же где из-за алгоритмических особенностей нужен не кидающий деструткор — использовали бы noexcept.
Даже была статья на эту тему — "Destructors That Throw: Evil, or Just Misunderstood? — Jon Kalb and Dave Abrahams" (оригинальный сайт выпилен, а копию/кэш я сходу не нашёл). Там была фраза, что-то в духе: "делать std::terminate в таких случаях это слишком драконовские меры".
Здравствуйте, jahr, Вы писали:
J>Много лет не могу для себя решить, как правильно себя вести при возникновении каких-то проблем во время выполнения деструктора. J>Например, для конкретики, есть у нас класс, в конструкторе читает данные из файла, в деструкторе — записывает модифицированные данные обратно в файл.
Запись в файл делай в отдельном методе Save(), или Flush(), или Close(), etc. В коде явно вызывай этот метод (потенциально бросающий исключение) в нормальном потоке выполнения, не полагаясь на деструктор.
В деструкторе тоже можешь попытаться вызвать этот метод или его содержимое (проверив, что объект «грязный» и сохранение действительно нужно, и не было ранее сделано вызовом Save()), но только обеспечив непросачивание исключений из этого вызова. Но лишь как last resort, не полагаясь на это при нормальном использовании класса.
Здравствуйте, smeeld, Вы писали:
S>Конструктор, предназначен для инициализации полей, деструктор, для освобождения ресурсов. S>Никаких тяжёлых операций, которые могут пройти неудачно, в них быть не должно. С++ не всесилен. S>При необходимости производить тяжёлые операции при инициализации и деинициализации, нужно S>делать всё по старинке как в Cи, разруливать в специализированных функциях: object.initialize(), object.deinitialize()
Очень жаль, а ведь счастье было так близко.) Про конструктор, кстати, не совсем понятно — почему не проводить там тяжелых операций если следить за тем, чтобы выполнение корректно завершилось при возникновении исключения посередине конструктора, basic exception safety вроде как для этого достаточно?
Если каждому объекту писать init/deinit кроме конструктора-деструктора, то все это не будет отличаться от С, по большому счету, мне кажется это очень сильно обесценивает все эти С++-навороты.( Лучше уж тогда ошибки игнорировать в деструкторе.(
Здравствуйте, Qbit86, Вы писали:
Q>Многи люди (и я в том числе) считают, что деструкторы — одна из главных фич C++.
Создание объекта с его автоматической инициализацией можно и в Cи реализовать макросами.
Для меня главной фичей С++ является STL, которые ускоряют и разработку, сам синтаксис С++
ничего кардинально лучшего по сравнению с Си не предлагает.
Здравствуйте, Qbit86, Вы писали:
Q>Здравствуйте, jahr, Вы писали:
Q>Запись в файл делай в отдельном методе Save(), или Flush(), или Close(), etc. В коде явно вызывай этот метод (потенциально бросающий исключение) в нормальном потоке выполнения, не полагаясь на деструктор. Q>В деструкторе тоже можешь попытаться вызвать этот метод или его содержимое (проверив, что объект «грязный» и сохранение действительно нужно, и не было ранее сделано вызовом Save()), но только обеспечив непросачивание исключений из этого вызова. Но лишь как last resort, не полагаясь на это при нормальном использовании класса.
Тогда мне придется каждое использование этого класса обкладывать try'ем, в catch'е которого вызывать этот метод Save, использование класса станет мучением, эти перехваты везде писать будет лениво, я начну это пропускать, начнутся ошибки и понесется.) Тогда уж проще вообще отказаться от исключений и работать только на кодах возврата как в С.)
Здравствуйте, jahr, Вы писали:
J>Как раз другие варианты и хотелось бы найти.)
Ну а какие еще могут быть варианты, если система возвращает ошибку, которую проигнорировать нельзя, а обработать невозможно?
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Тяжёлые операции в конструкторе — без проблем.
ага, знаем мы эти "без проблем" в случае статиков и глобалов..
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, jahr, Вы писали:
J>Много лет не могу для себя решить, как правильно себя вести при возникновении каких-то проблем во время выполнения деструктора.
J>Например, для конкретики, есть у нас класс, в конструкторе читает данные из файла, в деструкторе — записывает модифицированные данные обратно в файл. Делать все это хочется именно в конструкторе и деструкторе, чтобы эти действия выполнялись автоматически, при возникновении исключений при выполнении промежуточных действий — они правильно обрабатывались и т.п., как мне кажется, это стандартная более-менее практика.
J>И вот что делать, если в деструкторе не удалось выполнить запись в файл? Исключение кидать нельзя, игнорировать ошибку как-то неправильно, запись в лог — какое-то костыльное решение, не всегда уместно. J>Не выполнять в деструкторе никаких сложных действий? Но как же тогда концепция автоматического захвата-освобождения ресурсов в конструкторе-деструкторе, она вроде как считается правильной, и мой пример работы с файлом в нее укладывается, насколько я ее понимаю.
J>В общем, буду признателен, если вы поделитесь своими способами реализовать описанный в примере сценарий работы идеологически правильным способом.)
Нормального — идеологически — варианта нет.
Можно использовать (как большинство и делает) "грязный" вариант с глушением ошибок в деструкторе, и надеяться, что ошибка не произойдет. В принципе, в подавляющем большинстве случаев она и не происходит.
Если же нужна обработка, то только отдельный метод для финализации. Более многословно, но такой подход и более корректен.
Здравствуйте, CEMb,
CEM>2.2. [.....] Указатель на наш объект остался жив, мы можем создать новый объект-няньку, которому передать ссылку на наш объект и повторить пункт 2.2.
Мне представляется, что тут самой главной проблемой будет — как корректно "добить" наш объект, учитывая, что он уже частично "мертвый", частично еще "живой"? ("А что недострелили — так я не виноват." (с))
Много лет не могу для себя решить, как правильно себя вести при возникновении каких-то проблем во время выполнения деструктора.
Например, для конкретики, есть у нас класс, в конструкторе читает данные из файла, в деструкторе — записывает модифицированные данные обратно в файл. Делать все это хочется именно в конструкторе и деструкторе, чтобы эти действия выполнялись автоматически, при возникновении исключений при выполнении промежуточных действий — они правильно обрабатывались и т.п., как мне кажется, это стандартная более-менее практика.
И вот что делать, если в деструкторе не удалось выполнить запись в файл? Исключение кидать нельзя, игнорировать ошибку как-то неправильно, запись в лог — какое-то костыльное решение, не всегда уместно.
Не выполнять в деструкторе никаких сложных действий? Но как же тогда концепция автоматического захвата-освобождения ресурсов в конструкторе-деструкторе, она вроде как считается правильной, и мой пример работы с файлом в нее укладывается, насколько я ее понимаю.
В общем, буду признателен, если вы поделитесь своими способами реализовать описанный в примере сценарий работы идеологически правильным способом.)
Здравствуйте, jahr, Вы писали:
J>Много лет не могу для себя решить, как правильно себя вести при возникновении каких-то проблем во время выполнения деструктора.
ИМХО ответ на этот вопрос лежит в самом определении деструктора.
Деструктор вызывается при разрушении объекта. То есть, что бы не случилось в Вега деструкторе, остается в деструкторе. И вызываемый код doesn't care.
Отсюда и плясать.
J>И вот что делать, если в деструкторе не удалось выполнить запись в файл? Исключение кидать нельзя, игнорировать ошибку как-то неправильно, запись в лог — какое-то костыльное решение, не всегда уместно.
Всегда уместных решений особо и не бывает.
J>Не выполнять в деструкторе никаких сложных действий? Но как же тогда концепция автоматического захвата-освобождения ресурсов в конструкторе-деструкторе, она вроде как считается правильной, и мой пример работы с файлом в нее укладывается, насколько я ее понимаю.
"Запись в файл" — это не "освобождение ресурса". Это и правда весьма сложное действие, которое может обломаться. В зависимости от стратегии обработки ошбиок, внешней логике на это может быть пофигу, тогда в принципе это можно и в деструкторе делать.
А может и не пофигу, тогда подобному действию не место в деструкторе.
Здравствуйте, landerhigh, Вы писали:
L>Здравствуйте, jahr, Вы писали:
L>ИМХО ответ на этот вопрос лежит в самом определении деструктора. L>Деструктор вызывается при разрушении объекта. То есть, что бы не случилось в Вега деструкторе, остается в деструкторе. И вызываемый код doesn't care. L>Отсюда и плясать.
Как-то это неправильно получается — язык by-design подразумевает наличие каких-то "черных дыр" кода, это ж почти undefined behaviour.)
L>"Запись в файл" — это не "освобождение ресурса". Это и правда весьма сложное действие, которое может обломаться. В зависимости от стратегии обработки ошбиок, внешней логике на это может быть пофигу, тогда в принципе это можно и в деструкторе делать. L>А может и не пофигу, тогда подобному действию не место в деструкторе.
Ну, можно найти пример и не так явно сложный, например, — многие winapi-шные функции освобождения ресурсов имеют код возврата, при работе с этими ресурсами такие функции обычно помещают в деструктор, стандартно игнорируя этот код возврата, что периодически (хоть и редко) приводит к неочевидным ошибкам и сложной отладке.
Просто такая проблема стандартно возникает при работе со сценариями, похожими на описанный сценарий с файлом, я 10 лет назад спотыкался о них, выбирая писать ли С-подобный нечитаемый код или пожертвовать надежностью, и сейчас также задумываюсь об этом, не смотря на все бусты и С++14. Ведь решили же подобную застарелую проблему с ненужными копированиями при помощи movable-семантики, я подумал, что может я что-то пропустил и сейчас возможно красиво в плюсовом стиле прописать и эту стандартную задачу с чтением-записью в файл.)
Здравствуйте, jahr, Вы писали:
> Ведь решили же подобную застарелую проблему с ненужными копированиями при помощи movable-семантики
Это было решение проблемы самого С++, когда объекты копировались, Вы же предлагаете
решение глобальных проблем, типа невозможности выделения ресурсов ядром ОС, разруливать из
пользовательской программы на C++ автоматически, это С++ должен будет контролировать ядро ОС не больше
и не меньше.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, jahr, Вы писали:
J>>В общем, буду признателен, если вы поделитесь своими способами реализовать описанный в примере сценарий работы идеологически правильным способом.)
EP>Даже была статья на эту тему — "Destructors That Throw: Evil, or Just Misunderstood? — Jon Kalb and Dave Abrahams" (оригинальный сайт выпилен, а копию/кэш я сходу не нашёл). Там была фраза, что-то в духе: "делать std::terminate в таких случаях это слишком драконовские меры".
So there you have it. The reason we can’t have throwing destructors is that nobody worked out how to deal with multiple exceptions wanting to propagate through the same set of stack frames. Considering the fact that the program knows how to unwind from here, even if it doesn’t know exactly what to propagate, and the fact that it’s so easy to throw from a destructor by mistake, we think termination is a bit draconian.
Frankly, we don’t think it’s so hard to nail down the final details of how this should work. For example, it might be reasonable to simply drop the second exception on the floor and propagate the original one. Before you freak out, consider this: the second exception doesn’t change the unwinding process in any way, at least, not until the exception is caught, and the original failure is still the root cause of the current unwind. The program or the user can likely deal just as well with that root cause without knowing anything about the second exception.
This being C++, we expect someone to want more control over that second exception, so in our next installment, we’ll consider some alternatives. For now, we leave you with the suggestion that maybe destructors that throw are not truly Evil™, but just misunderstood.
Здравствуйте, smeeld, Вы писали:
>> Ведь решили же подобную застарелую проблему с ненужными копированиями при помощи movable-семантики
S>Это было решение проблемы самого С++, когда объекты копировались, Вы же предлагаете S>решение глобальных проблем, типа невозможности выделения ресурсов ядром ОС, разруливать из S>пользовательской программы на C++ автоматически, это С++ должен будет контролировать ядро ОС не больше S>и не меньше.
Нет, ядро здесь совсем не при чем. В С++ есть возможность автоматически выполнять какие-то операции (деструкторы), но на эти операции наложено очень сильное ограничения — они не могу возвращать значений и кидать исключения, что при аккуратном отношении к логике выполнения практически лишает программиста возможности использовать это автоматическое выполнение кода за исключением самых тривиальных случаев.
Здравствуйте, jahr, Вы писали:
L>>А объект, в свою очередь, способен гарантированно подтереть за собой в деструкторе. L>>Это же очевидно.
J>Вопрос как раз в том, что делать если подтереть не получилось.)
Застрелиться aka terminate(). Есть другие варианты?
Здравствуйте, jahr, Вы писали:
Q>>Запись в файл делай в отдельном методе Save(), или Flush(), или Close(), etc. В коде явно вызывай этот метод (потенциально бросающий исключение) в нормальном потоке выполнения, не полагаясь на деструктор. Q>>В деструкторе тоже можешь попытаться вызвать этот метод или его содержимое (проверив, что объект «грязный» и сохранение действительно нужно, и не было ранее сделано вызовом Save()), но только обеспечив непросачивание исключений из этого вызова. Но лишь как last resort, не полагаясь на это при нормальном использовании класса.
J>Тогда мне придется каждое использование этого класса обкладывать try'ем, в catch'е которого вызывать этот метод Save, использование класса станет мучением, эти перехваты везде писать будет лениво, я начну это пропускать, начнутся ошибки и понесется.) Тогда уж проще вообще отказаться от исключений и работать только на кодах возврата как в С.)
Но методы flush и close у стандартного fstream примерно так и работают. fstream делает попытку «сохранить» (сбросить буффер) в деструкторе, но никак не уведомляют в случае фейла. При ручном закрытии успешность close'а можно опросить (badbit, failbit).
Здравствуйте, landerhigh, Вы писали:
J>>Как раз другие варианты и хотелось бы найти.) L>Ну а какие еще могут быть варианты, если система возвращает ошибку, которую проигнорировать нельзя, а обработать невозможно?
почему это?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, landerhigh, Вы писали:
J>>>Как раз другие варианты и хотелось бы найти.) L>>Ну а какие еще могут быть варианты, если система возвращает ошибку, которую проигнорировать нельзя, а обработать невозможно? V>почему это?
У меня есть смутное подозрение, что этот разговор опять смещается в сторону сферического случая в вакууме с обработкой ошибки от функции закрытия файла.
Здравствуйте, landerhigh, Вы писали:
L>>>Ну а какие еще могут быть варианты, если система возвращает ошибку, которую проигнорировать нельзя, а обработать невозможно? V>>почему это? L>У меня есть смутное подозрение, что этот разговор опять смещается в сторону сферического случая в вакууме с обработкой ошибки от функции закрытия файла.
я не понял почему ошибку проигнорировать нельзя? кто сказал что нельзя?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, jahr, Вы писали:
J>Тогда мне придется каждое использование этого класса обкладывать try'ем, в catch'е которого вызывать этот метод Save, использование класса станет мучением, эти перехваты везде писать будет лениво, я начну это пропускать, начнутся ошибки и понесется.) Тогда уж проще вообще отказаться от исключений и работать только на кодах возврата как в С.)
Учитывая, что обработка в catch это очень затратная процедура, то проверка в кодах возрата,
как в Си, будет явно производительней, за исключением случаев, когда проверяемая функция вызывается
в циклах с миллионами итераций.
Здравствуйте, Vain, Вы писали:
L>>>>Ну а какие еще могут быть варианты, если система возвращает ошибку, которую проигнорировать нельзя, а обработать невозможно? V>>>почему это? L>>У меня есть смутное подозрение, что этот разговор опять смещается в сторону сферического случая в вакууме с обработкой ошибки от функции закрытия файла. V>я не понял почему ошибку проигнорировать нельзя? кто сказал что нельзя?
Ключевое слово выделено вверху. Это то, о чем ТС спрашивал.
Здравствуйте, landerhigh, Вы писали:
V>>я не понял почему ошибку проигнорировать нельзя? кто сказал что нельзя? L>Ключевое слово выделено вверху. Это то, о чем ТС спрашивал.
система вроде вообще не может вернуть ошибку, которую нельзя проигнорировать, для чего тогда хуки?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Vain, Вы писали:
L>>Ключевое слово выделено вверху. Это то, о чем ТС спрашивал. V>система вроде вообще не может вернуть ошибку, которую нельзя проигнорировать, для чего тогда хуки?
Здравствуйте, jahr, Вы писали:
J>И вот что делать, если в деструкторе не удалось выполнить запись в файл? Исключение кидать нельзя, игнорировать ошибку как-то неправильно, запись в лог — какое-то костыльное решение, не всегда уместно. J>Не выполнять в деструкторе никаких сложных действий? Но как же тогда концепция автоматического захвата-освобождения ресурсов в конструкторе-деструкторе, она вроде как считается правильной, и мой пример работы с файлом в нее укладывается, насколько я ее понимаю.
А можешь расписать алгоритм, как в идеале ты это видишь?
У меня для приближения к идеалу тут 2 перпендикулярных пункта:
1. вынести всю работу с файлом в отдельный класс, который и будет заниматься разными ситуациями в своём деструкторе. Это проблему не решит, но наведёт больше порядка и поделит проблему надвое.
2. если нужна ситуация, которую надо обработать _не_ в конструкторе/деструкторе, при этом хочется использовать этот механизм, то я бы завёл некий класс-няньку, который в конструкторе создавал бы объект моего класса, или принимал ссылку на него и тогда ничего не делал. А в деструкторе звал бы release или что там нужно для освобождения. При этом, да, нужно где-то хранить ещё указатель на сам объект нашего класса(можно завести пул объектов, куда смотрят няньки). В результате:
2.1. в нянька-конструкторе, если что-то пошло не так, мы просто не получим наш объект. Всё ок.
2.2. в нянька-деструкторе, если что-то пошло не так, объект-нянька погибнет, но сможет сообщить, что освобождение ресурсов не завершено, или просто сбросит свой дочерний объект в пул. Указатель на наш объект остался жив, мы можем создать новый объект-няньку, которому передать ссылку на наш объект и повторить пункт 2.2.
коряво, да, но в _нашем_ классе проблема решена но это всё, imho, "вам шашечки или ехать?", кому что больше хочется.
если конкретно про файл, то мне кажется, ситуацию с завершением можно решить в деструкторе нормально, т.е. записать в файл, если не записалось, записать в другой, или в лог, или окно показать или ещё что, в зависимости от ситуации и того, что хотел автор.
Здравствуйте, Vlad_SP, Вы писали:
CEM>>2.2. [.....] Указатель на наш объект остался жив, мы можем создать новый объект-няньку, которому передать ссылку на наш объект и повторить пункт 2.2.
V_S>Мне представляется, что тут самой главной проблемой будет — как корректно "добить" наш объект, учитывая, что он уже частично "мертвый", частично еще "живой"? ("А что недострелили — так я не виноват." (с))
Ага, вот потому и пункт 1 — минимизировать классы таких объектов. Т.е. или жив или мёртв(отсутствует в пуле). Если жив, убивается ровно одним однозначным способом. К примеру, файл полностью записан и закрыт.
Здравствуйте, CEMb, Вы писали:
CEM> К примеру, файл полностью записан и закрыт.
В файл записалась только половина данных, дальше кончилось место на диске.
На сервер в Тимбукту передана только половина данных, дальше пьяный экскаваторщик Вася в Мухосранске порвал магистральный кабель.
И так далее....
Здравствуйте, Vlad_SP, Вы писали:
V_S>В файл записалась только половина данных, дальше кончилось место на диске. V_S>На сервер в Тимбукту передана только половина данных, дальше пьяный экскаваторщик Вася в Мухосранске порвал магистральный кабель. V_S>И так далее....
Всем такие события поздновато разруливать в деструкторе.
В принципе можно, но под этим деструктором должна быть еще куча уровней, которые умеют это разруливать сами.
Здравствуйте, landerhigh, Вы писали:
EP>>Даже была статья на эту тему — "Destructors That Throw: Evil, or Just Misunderstood? — Jon Kalb and Dave Abrahams" (оригинальный сайт выпилен, а копию/кэш я сходу не нашёл). Там была фраза, что-то в духе: "делать std::terminate в таких случаях это слишком драконовские меры". L>Почитал. Принципиальной невозможности аккумулировать исключения имхо особенно нет.
Альтернативный вариант, озвученный в статье, это игнорировать все кроме первого исключения.
Думаю можно вызывать глобальный пользовательский callback для игнорируемых исключений.
L>Но у меня другой вопрос — а что с объектом-то? Вот выкинул он исключение на полпути деструктора. И стал зомби. Вернуть к жизни его нельзя. Добить? Как? Разрешать рекуррентный вызов деструктора, чтобы наверняка добить? Так это ничем не отличается от цепочки cleanup() — delete. L>Или принять ограничения, что исключение в деструкторе — чисто информационное и объект, выбросивший его, гаратированно убит?
Например в само исключение можно запаковывать все внутренности объекта. То есть например file handle будет внутри объекта исключения.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Экспериментальная альтернатива — двухфазная семантика разрушения. Вместо деструктора пишутся два метода release и deferred — release выполняется всегда в самом конце, deferred выполняется перед release и только в случае если не летит исключение. В итоге пример выше переписывается в: EP>
по теме. _NN>Стандартизировать это еще не собираются в C++17
std::uncaught_exceptions уже в текущем Draft'е — 18.8.4.
Но для кидающих деструкторов его всё же следует использовать с осторожностью. Дело в том что объект созданный в одном scope может переехать в другой — в котором будет другое начальное значение uncaught_exceptions.
Для реализаций scope(failure) и scope(success) — это не проблема, так как их объекты-guard'ы живут в одном scope.
Здравствуйте, Vlad_SP, Вы писали:
CEM>> К примеру, файл полностью записан и закрыт.
V_S>В файл записалась только половина данных, дальше кончилось место на диске. V_S>На сервер в Тимбукту передана только половина данных, дальше пьяный экскаваторщик Вася в Мухосранске порвал магистральный кабель. V_S>И так далее....
Ну и нормально. Файл полностью записать и закрыт? Нет — объект отправляем в пул. Не вижу проблем.
Здравствуйте, landerhigh, Вы писали:
L>Всем такие события поздновато разруливать в деструкторе. L>В принципе можно, но под этим деструктором должна быть еще куча уровней, которые умеют это разруливать сами.
Ну вот я для того пункт 2 и придумал, чтобы это был и деструктор и не наш, т.е. у нашего объекта ещё было время сделать корректно все свои дела перед своим деструктором. Вообще мне такая схема кажется кривой и избыточной, но больше пока ничего не придумал.
по теме. _NN>>Стандартизировать это еще не собираются в C++17
EP>std::uncaught_exceptions уже в текущем Draft'е — 18.8.4.
Это в курсе.
Я про человечный синтаксис для TWO_STAGE_DESTRUCTOR_DEFERRED, TWO_STAGE_DESTRUCTOR_RELEASE.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Альтернативный вариант, озвученный в статье, это игнорировать все кроме первого исключения.
Ну это просто праздник какой-то!
EP>Думаю можно вызывать глобальный пользовательский callback для игнорируемых исключений.
Так его и так никто не мешает сделать прямо сейчас
L>>Но у меня другой вопрос — а что с объектом-то? Вот выкинул он исключение на полпути деструктора. И стал зомби. Вернуть к жизни его нельзя. Добить? Как? Разрешать рекуррентный вызов деструктора, чтобы наверняка добить? Так это ничем не отличается от цепочки cleanup() — delete. L>>Или принять ограничения, что исключение в деструкторе — чисто информационное и объект, выбросивший его, гаратированно убит? EP>Например в само исключение можно запаковывать все внутренности объекта. То есть например file handle будет внутри объекта исключения.
Это же нарушает инкапсуляцию. Да и вся сила RAII в том, что клиенты не обязаны знать, что под капотом объекта там что-то такое происходит. А тут — красиво заворачиваем кишочки объекта в праздничную упаковку и перебрасываем ее через забор, авось кто поймает
Здравствуйте, T4r4sB, Вы писали:
TB>В деструкторе записывать успешность результата во внешнюю переменную (ссылка на которую даётся в конструкторе). Потом проверять эту переменную.
Можно даже пойти дальше и сделать ленивый проброс исключений.
class A
{
//.....
~A() {
if(!m_file.save())
LASY_THROW(FileSaveException());
}
};
///...........
{
A a;
// do some stuff
// ....
}
RETHROW();
Но это, конечно, тоже полумера и не всегда уместная.
Здравствуйте, landerhigh, Вы писали:
EP>>Альтернативный вариант, озвученный в статье, это игнорировать все кроме первого исключения. L>Ну это просто праздник какой-то!
Всяко лучше чем std::terminate.
EP>>Думаю можно вызывать глобальный пользовательский callback для игнорируемых исключений. L>Так его и так никто не мешает сделать прямо сейчас
Сейчас игнорируются (программистами) все исключения в деструкторах, и нет общего надёжного способа узнать когда можно кидать исключение, а когда будет std::terminate.
В этом же варианте по сути вместо std::terminate будет вызван пользовательский callback.
L>>>Но у меня другой вопрос — а что с объектом-то? Вот выкинул он исключение на полпути деструктора. И стал зомби. Вернуть к жизни его нельзя. Добить? Как? Разрешать рекуррентный вызов деструктора, чтобы наверняка добить? Так это ничем не отличается от цепочки cleanup() — delete. L>>>Или принять ограничения, что исключение в деструкторе — чисто информационное и объект, выбросивший его, гаратированно убит? EP>>Например в само исключение можно запаковывать все внутренности объекта. То есть например file handle будет внутри объекта исключения. L>Это же нарушает инкапсуляцию. Да и вся сила RAII в том, что клиенты не обязаны знать, что под капотом объекта там что-то такое происходит. А тут — красиво заворачиваем кишочки объекта в праздничную упаковку и перебрасываем ее через забор, авось кто поймает
Я описывал чисто технические внутренности, в реальности этот кидаемый file handle может быть завёрнут в полноценную RAII обёртку, которая автоматически закроет файл даже если наверху это исключение не обработается специальным образом (например будут ловить только базовое std::exception).
То есть мой поинт не в том что нужно кидаться голыми кишками, а в том что нет проблемы в том что мы находимся в деструкторе объекта File, и после выброса исключения он прекратит своё существование. Мы можем запоковать всё необходимое во внутрь исключения, тем самым продлив время жизни необходимых ресурсов на время раскрутки стэка — а уже наверху смогут принять конкретные решения.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
L>>Ну это просто праздник какой-то!
EP>Всяко лучше чем std::terminate.
Чем же лучше? Что вобще может выдать ошибку в деструкторе? "Не могу вернуть память"? Бугага. "Ниасилил записать"? По логике, если это происходит в деструкторе, то обычно это значит, что "ну и чорд с ним". А вот если вовсе не "чорд с ним", то terminate — наше фсио!
Я, конечно, знаю примеры, когда деструктор вызывал такую длинную цепочку вызовов, что из нее могло прилететь все, что угодно. Но это вовсе не значит, что нужно так делать. И то, что это спрятано внутри вызова деструктора, как бы намекает, что код, использующий этот класс, вовсе не обязан быть в курсе всей происходящей за кулисами кухни.
EP>Сейчас игнорируются (программистами) все исключения в деструкторах, и нет общего надёжного способа узнать когда можно кидать исключение, а когда будет std::terminate.
В принципе, в каждом таком треде все это сводится к одному-единственному примеру. "Класс закрывает файл в деструкторе, а вдруг закрытие файла обломается". Я же считаю, что это сферическое программирование в вакууме. Если при закрытии файла выскочила ошибка, то что-то сделать, чтобы исправить эту конкретную ошибку, пользовательский код уже вряд ли сможет.
EP>В этом же варианте по сути вместо std::terminate будет вызван пользовательский callback.
L>>>>Но у меня другой вопрос — а что с объектом-то? Вот выкинул он исключение на полпути деструктора. И стал зомби. Вернуть к жизни его нельзя. Добить? Как? Разрешать рекуррентный вызов деструктора, чтобы наверняка добить? Так это ничем не отличается от цепочки cleanup() — delete. L>>>>Или принять ограничения, что исключение в деструкторе — чисто информационное и объект, выбросивший его, гаратированно убит? EP>>>Например в само исключение можно запаковывать все внутренности объекта. То есть например file handle будет внутри объекта исключения.
Не-не-не, что с оригинальным объектом? Вот на полпути в деструкторе вылетает исключение. Оно делает невозможным как продолжение выполнения кода деструктора, так и собственно освобождение остальных ресурсов объекта. То есть логика завершения работы объекта идет побоку, а там может быть чего понавороченнее, чем просто "закрыть файл".
EP>То есть мой поинт не в том что нужно кидаться голыми кишками, а в том что нет проблемы в том что мы находимся в деструкторе объекта File, и после выброса исключения он прекратит своё существование. Мы можем запоковать всё необходимое во внутрь исключения, тем самым продлив время жизни необходимых ресурсов на время раскрутки стэка — а уже наверху смогут принять конкретные решения.
Что-то франкенштейн какой-то получается, честное слово. Одна нога тут, другая там, а голова вообще в Филадельфии
Как раз появилась статья на хабре на эту тему — http://habrahabr.ru/post/270545/, как я понимаю написана по мотивам рассуждений Александреску на эту тему, решил здесь оставить ссылку на случай если кто-то еще будет чимтать этот тред когда-то потом.)