Re[12]: 1) Родовая травма: исключеиния в деструкторах
От: landerhigh Пират  
Дата: 16.10.12 20:40
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, landerhigh, Вы писали:


L>>Почему не стоит? Этот случай показывает, что очень даже стоит.

E>Тогда почему это RAII тут не при чём?

Потому что эта обертка — нифига не RAII. Period.
Хотя use case очень хороший. Помогает провести границу применимости RAII.

L>>Просто потому, что данное условие в данной обертке сработает в одном — единственном случае, когда про поток забыли. Поскольку про него забыли, то безопасно предположить, что он уже вовсю стреляет по памяти.

E>Этот вывод можно как-то обоснвоать? И почему считается, что до деструктора thread он стрелял по памяти безопасно,а теперь стал опасно?

Медленно и по буквам — ловушка в деструкторе этой обертки срабатывает только в одном случае — если про поток забыли. К моменту разрушения обертки поток либо должен быть завершен, отпущен в свободное плавание. Если программер забыл позаботиться о потоке, то, скорее всего он много чего еще забыл. рассчитывать на безопасное продолжение работы в этом случае нельзя. Энд оф стори.

L>>Единственно верное решение в данном случае — совершить харакири. Обработчик terminate дамп запишет и все, баиньки.

E>Видимо это догмат какой-то веры...

Видимо, просто кто-то что-то не догоняет.

E>Кстати, а как при таком подходе гарантировать освобождение критических ресурсов? terminate их же не освободит?


Каких ресурсов? Их уже твой забытый поток покромсал в салат.

L>>Исключение из деструктора?

E>Ну исключения из деструктора плохи только тем, что могут вызвать termainate...

Кто здесь?

L>>Завершение потоков из дестурктора — очень, очень плохая идея.

E>Я с этим и не спорю. Именно поэтому мне представляется странной идея рулить ими через RAII...

А что, кто-то предлагает так делать?

L>>Порой мне кажется, что каждый должен наступить на эти грабли сам, чтобы, так сказать, прочувствовать. Эта обертка даст тебе по рукам гораздо раньше, нежели тебе прилетит в лоб от забытого потока (или коллеги, который ловил баг неделю).


E>А как можно неделю ловить "забытый поток"? Имеющиеся в любой момент времени потоки можно же перечислить?


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

E>Кроме того, я не понимаю, почему поток, на который у нас в программе не омталось хэнедла считается забытым? Нормальные потоки завершают себя САМИ, а не через хэндл их кто-то прибивает же...


Это ты сейчас с кем разговаривал?

E>То, что мы выбросили хэндл потока, говоит всего лишь о том, что мы не хотим ждать его завершения.

E>Почему мы явно должны сказать, что не хотим?

Потому что. Это ловушка для дурака. Если не хочешь ждать завершения — сделай ему detach или чего у него там есть в API. Если забыл, то, скорее всего, ты много чего еще "забыл".

E>Кроме того, даже если мы хотим с потоком как-то взаимодействовать, то даже это не обозначает, что мы всё равно хотим ждать его завершения. По умолчанию же потоки всё-таки должны звершаться САМИ...


Забыл подписаться, кэп.
www.blinnov.com
Re[13]: 1) Родовая травма: исключеиния в деструкторах
От: Erop Россия  
Дата: 16.10.12 20:58
Оценка:
Здравствуйте, landerhigh, Вы писали:

L>Потому что эта обертка — нифига не RAII. Period.

L>Хотя use case очень хороший. Помогает провести границу применимости RAII.

Ну тогда я дальше и не спорю. Потоки -- пример ресурсов, которые для RAII негодятся...

L>А что, кто-то предлагает так делать?

Ну в этом обсуждениии они же всплыли?


L>Медленно и по буквам — ловушка в деструкторе этой обертки срабатывает только в одном случае — если про поток забыли. К моменту разрушения обертки поток либо должен быть завершен, отпущен в свободное плавание. Если программер забыл позаботиться о потоке, то, скорее всего он много чего еще забыл. рассчитывать на безопасное продолжение работы в этом случае нельзя. Энд оф стори.

А если не забыла, а не успели?..

L>Видимо, просто кто-то что-то не догоняет.

Ну как объясняют, так и догоняют

L>Каких ресурсов?

Ну там реактор заглушить, или пятьсот гектар временных файлов потереть...
Ресурсы они разные бывают

L>Их уже твой забытый поток покромсал в салат.

Ну недо писать такие потоки, которые сразу, как где-то перестали за ними следить через хэндл, сразу бросаются что-то там кромсать...
В РФ это, кроме всего прочего, ещё и УК, кстати...


L>Можно и месяц. Ты, очевидно, не совсем понимаешь, о чем я говорю. Ничего, будет и на твоей улице праздник, когда чертовщину, вызванную маааленьким таким расстрелом памяти из потока, которому "забыли" вовремя сказать, что пора бы уже и закругляться, будете всей конторой с отладчиком наперевес отлавливать.


Думаю, что не будем...

L>Это ты сейчас с кем разговаривал?

Извини, я думал, что ты в теме...

L>Потому что. Это ловушка для дурака. Если не хочешь ждать завершения — сделай ему detach или чего у него там есть в API. Если забыл, то, скорее всего, ты много чего еще "забыл".

Странное обобщение. А если ты, например, показывал прогресс-бар, а потом тебе нажали "отмена".
Ты послал потоку сообщение, типа тухни, и тут случился logic_error. Почему надо падать в терминэйт?


L>Забыл подписаться, кэп.

Тогда я всё равно не понимаю, при чём тут расстрел памяти и забытость потока?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[14]: 1) Родовая травма: исключеиния в деструкторах
От: landerhigh Пират  
Дата: 16.10.12 23:20
Оценка:
Здравствуйте, Erop, Вы писали:

L>>Потому что эта обертка — нифига не RAII. Period.

L>>Хотя use case очень хороший. Помогает провести границу применимости RAII.

E>Ну тогда я дальше и не спорю. Потоки -- пример ресурсов, которые для RAII негодятся...


Ну зачем же так огульно. Есть use cases, где очень даже годятся. И даже с потоками.

L>>А что, кто-то предлагает так делать?

E>Ну в этом обсуждениии они же всплыли?
E>

L>>Медленно и по буквам — ловушка в деструкторе этой обертки срабатывает только в одном случае — если про поток забыли. К моменту разрушения обертки поток либо должен быть завершен, отпущен в свободное плавание. Если программер забыл позаботиться о потоке, то, скорее всего он много чего еще забыл. рассчитывать на безопасное продолжение работы в этом случае нельзя. Энд оф стори.

E>А если не забыла, а не успели?..


"Не успели" есть частный случай "забыли". Задача сведена к предыдущей.

L>>Каких ресурсов?

E>Ну там реактор заглушить, или пятьсот гектар временных файлов потереть...

Интересно, что ты реактор упомянул. Из нас двоих кое-кто один действительно пишет софт для электростанций. Не атомных, к счастью, а то хрен мне был бы поперек рыла, а не RSDN на рабочем месте.

E>Ресурсы они разные бывают


Так вот, по поводу реактора. Пока ты там подтираешь 100500 гектар временных файлов, поток, которому ты не успел забыл сказать, что пора домой, тихонько продолжал вытаскивать стержни из актвной зоны. К моменту, когда ты блестяще избавишься от временных файлов, всем будет уже кагбе пофиг.

L>>Их уже твой забытый поток покромсал в салат.

E>Ну недо писать такие потоки, которые сразу, как где-то перестали за ними следить через хэндл, сразу бросаются что-то там кромсать...

Поток, которому "не успели" вовремя сказать, что пора завершиться, остается один на один с данными, которые уже могут быть потерты и с объектами, котрые уже тоже давно в лучшем случае зомби. То, что в результате он чего-то там покромсал, вина исключительно программиста.

E>В РФ это, кроме всего прочего, ещё и УК, кстати...


Тебе все хиханьки да хаханьки, а у нас перед релизом обязательный safety review. Потому что косяк в коде имеет вполне реальные шансы превратиться в невовремя замкнутый разрыватель на 600 КВ линии переменного тока с сопутствующими спецэфеектами и органическими удобрениями вдоль линии.

L>>Можно и месяц. Ты, очевидно, не совсем понимаешь, о чем я говорю. Ничего, будет и на твоей улице праздник, когда чертовщину, вызванную маааленьким таким расстрелом памяти из потока, которому "забыли" вовремя сказать, что пора бы уже и закругляться, будете всей конторой с отладчиком наперевес отлавливать.


E>Думаю, что не будем...


У вас все впереди. Не зарекайся.

L>>Это ты сейчас с кем разговаривал?

E>Извини, я думал, что ты в теме...

Давай договоримся — ты не строишь из себя дурочку не по делу, а я не пишу ничего о счете и косточковых фруктах?

L>>Потому что. Это ловушка для дурака. Если не хочешь ждать завершения — сделай ему detach или чего у него там есть в API. Если забыл, то, скорее всего, ты много чего еще "забыл".

E>Странное обобщение. А если ты, например, показывал прогресс-бар, а потом тебе нажали "отмена".
E>Ты послал потоку сообщение, типа тухни, и тут случился logic_error. Почему надо падать в терминэйт?

logic еггог? Если только у программиста.
Если твой лоджик еггог приведет к размотке стека и удалению владельца потока, то твоя прямая обязанность удостовериться в корректности состояния потока. В большинстве случаев это означает сигнал к завершению потока и ожидание его завершения. В некоторых случаях можно просто просигнализировать и не ждать. В редких случаях поток можно отпустить на вольные хлеба.
Если ты не сделал ничего из вышеперечисленного, то ты, скорее всего, не подумал. Самое безопасное, что в этом случае можно сделать — это упасть. Что обсуждаемая обертка и делает.

L>>Забыл подписаться, кэп.

E>Тогда я всё равно не понимаю, при чём тут расстрел памяти и забытость потока?
www.blinnov.com
Re[15]: 1) Родовая травма: исключеиния в деструкторах
От: Erop Россия  
Дата: 17.10.12 12:23
Оценка:
Здравствуйте, landerhigh, Вы писали:


L>Так вот, по поводу реактора. Пока ты там подтираешь 100500 гектар временных файлов, поток, которому ты не успел забыл сказать, что пора домой, тихонько продолжал вытаскивать стержни из актвной зоны. К моменту, когда ты блестяще избавишься от временных файлов, всем будет уже кагбе пофиг.


А не надо писать такие потоки, которые без всякого смысла и толка вытаскивают стержни из зон...
Если у тебя есть поток, который рулит реактором, например, по потоку на реактор, то он должен рулить даже если основная программа затупит, ради того в отдельный поток и вынесен. И чего его убивать, что бы реактором вообще не рулили, если в основной программе где-то контрола на найдётся в диалоге, например, лично мне не понятно.

E>>Ну недо писать такие потоки, которые сразу, как где-то перестали за ними следить через хэндл, сразу бросаются что-то там кромсать...


L>Поток, которому "не успели" вовремя сказать, что пора завершиться, остается один на один с данными, которые уже могут быть потерты и с объектами, котрые уже тоже давно в лучшем случае зомби. То, что в результате он чего-то там покромсал, вина исключительно программиста.


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

E>>В РФ это, кроме всего прочего, ещё и УК, кстати...


L>Тебе все хиханьки да хаханьки, а у нас перед релизом обязательный safety review. Потому что косяк в коде имеет вполне реальные шансы превратиться в невовремя замкнутый разрыватель на 600 КВ линии переменного тока с сопутствующими спецэфеектами и органическими удобрениями вдоль линии.


Дык потому и не надо писать опасный код...
Кстати, а если ваше ПО уходит по терминэйту в астрал, то что со станцией случается?

E>>Думаю, что не будем...

L>У вас все впереди. Не зарекайся.

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

L>Давай договоримся — ты не строишь из себя дурочку не по делу, а я не пишу ничего о счете и косточковых фруктах?

Ты намекаешь на то, что ты слил? Я думал тебе по существу вопроса что-то обсудить хочется, если ты за косточкой сюда пришёл, то иди себе, соси свою косточку. Ты слил...

L>>>Потому что. Это ловушка для дурака. Если не хочешь ждать завершения — сделай ему detach или чего у него там есть в API. Если забыл, то, скорее всего, ты много чего еще "забыл".

Есть вариант круче, просто не пользоваться этим придурошным классом.

L>logic еггог? Если только у программиста.

И что?

E>>Тогда я всё равно не понимаю, при чём тут расстрел памяти и забытость потока?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[18]: 1) Родовая травма: исключеиния в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 17.10.12 13:52
Оценка:
Здравствуйте, Ku-ku, Вы писали:

EP>>Ну вот допустим вызовы deferred расставлялись бы компилятором

KK>То есть что-то вроде этого?
KK>
class File
KK>{
KK>public:
KK>    /*...*/
KK>    ~File(std::finalize_t) { flush(); }
KK>    ~File() { releaseResources(); }
KK>};

KK>void foo(const wchar_t* filename)
KK>{
KK>    {
KK>        File file(filename);
KK>        /*...*/
KK>    } // calls file.File::~File(std::finalize_t()), then file.File::~File()
KK>}


да, примерно так.

KK>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?


По исключениям. Ну он же знает когда terminate звать
Ну или как-то же scope(success) в языке D реализован.

KK>Мой поинт в том, что клиент сам должен принимать решение насчёт вызова deferred, а автор класса не должен навязывать какую-то "единственно правильную" стратегию. Например, я могу захотеть выйти из блока через return, не вызывая deferred.


Ну вот сейчас авторы классов навязывают стратегию — деструкторы тоже вызываются по return.
А вот в каких случаях не нужно вызывать flush по return, но нужно close — трудно представляю.
Но суть не в этом — выбор-то всё-равно есть. Автор класса может сделать доступным метод flush, и в private хранить и проверять is_flushed..

EP>>Какая разница для пользователя, кто делает эти вызовы — компилятор, или сгенерированный макросами деструктор?

KK>Разница будет хотя бы в раздельной спецификации исключений для ~File(std::finalize_t) и ~File().

Это действительно минус текущей реализации — согласен

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


Может да, а может и не так важно.
Меня волнует больше другой аспект: вот допустим есть контейнер для таких объектов — какое поведение должно быть у деструктора контейнера?
Видимо такое, которое подражает поведению стэковых объектов — то есть если во время деструкции произошло исключению в одном из под-объектов, то его нужно временно поймать, тихо (без deferred) погасить другие, и сделать throw;
Вопрос в том как вызывать остальные без deferred — ведь они фактически вызывается не во время раскрутки стэка.
То есть нужно что-то типа
try
{
   // try to destroy all with deferred
   // ...
   obj->~T();
}
catch(...)
{
   // destroy rest without deferred:
   // ..
   obj_some->~T(true);
   throw;
}

где true — это аргумент для Unwinding Aware Destructor. Причём он должен передаваться и всем родителям и членам.
Сейчас это можно сделать модификацией unwinding_indicator — добавить TLS счётчик, и сделать макрос для подражания синтаксису выше. Не очень красиво, но должно работать. В общем надо такой контейнер для примера реализовать.

KK>>>>>Может, ему взбредёт в голову сделать объект статическим или thread_local. Зачем ему тогда flush в деструкторе, и как он будет ловить возможные исключения оттуда?

EP>>>>Зависит от use-case — может пользователю будет достаточно того, что программа не вернёт EXIT_SUCCESS на не пойманном исключении.
KK>>>Итог непойманного исключения на таких уровнях — вызов std::terminate. А поймать их никак нельзя, потому что к моменту вызова деструктора управление уже успеет покинуть любой try блок в потоке, где происходит этот вызов.
EP>>Ну так я же и говорю, для кого-то может достаточно return code != EXIT_SUCCESS :
KK>Я смотрю на это как на потенциальную замаскированную ошибку.

Контр-пример — как ловить исключения из конструктора такого объекта?

EP>>Более того, необходимо помнить об транзитивности кидающих деструкторов.

KK>Если бы была поддержка на уровне компилятора, думаю, эту проблему можно было бы легко разрулить. Типа финализатор неявно вызывал бы финализаторы подобъектов, а деструктор — деструкторы подобъектов.

На счёт того что лучше — сначала всё deferred, а потом все деструкторы, или вперемешку — имхо лучше вперемешку, так как есть сейчас, но я это ещё не рассматривал.
А транзитивность я упомянул в контексте того, что допустим есть некий класс A, он используется по всей программе, никогда ничего из деструктора не кидал, код его использующий на это не расчитан. Теперь если где-то в глубине его композиции добавится член кидающий из деструктора, то сам этот класс A будет иметь кидающий деструктор.
Re[9]: 1) Родовая травма: исключеиния в деструкторах
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 17.10.12 15:05
Оценка: +3
Здравствуйте, enji, Вы писали:

E> Ну дык если надо что-то неубиваемое — создаем отдельный вотчдог, который перезапускает рабочий процесс, если тот перестал подавать признаки жизни.


Ага, а для контроля этого вотчдога — ещё один вотчдог Видел я такие конструкции в продакшене — довольно унылое зрелище...
[КУ] оккупировала армия.
Re[19]: 1) Родовая травма: исключеиния в деструкторах
От: Ku-ku  
Дата: 17.10.12 16:47
Оценка: 12 (1)
Здравствуйте, Evgeny.Panasyuk, Вы писали:

KK>>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?


EP>По исключениям.


У меня возникают сомнения насчёт того, что этого достаточно.

KK>>Мой поинт в том, что клиент сам должен принимать решение насчёт вызова deferred, а автор класса не должен навязывать какую-то "единственно правильную" стратегию. Например, я могу захотеть выйти из блока через return, не вызывая deferred.


EP>Ну вот сейчас авторы классов навязывают стратегию — деструкторы тоже вызываются по return.


Только для автоматических переменных. Никто не мешает управлять временем жизни объекта вручную.

EP>А вот в каких случаях не нужно вызывать flush по return, но нужно close — трудно представляю.


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

EP>Но суть не в этом — выбор-то всё-равно есть. Автор класса может сделать доступным метод flush, и в private хранить и проверять is_flushed..


Как выполнить только очистку ресурсов?

EP>Меня волнует больше другой аспект: вот допустим есть контейнер для таких объектов — какое поведение должно быть у деструктора контейнера?


При поддержке со стороны компилятора можно было бы сделать так: финализатор зовёт финализаторы, деструктор зовёт деструкторы.

С комбинированными деструкторами можно поступить вот таким образом

template <typename T>
void destroy(T& t)
{
    t.~T();
}

template <typename BidirectionalIterator>
void destroyRange(BidirectionalIterator first, BidirectionalIterator last)
{
    while (last != first)
        (destroy)(*--last);
    SCOPE_EXIT(&) // or SCOPE_FAILURE(&)
    {
        while (last != first)
            (destroy)(*--last);
    };
}


KK>>>>>>Может, ему взбредёт в голову сделать объект статическим или thread_local. Зачем ему тогда flush в деструкторе, и как он будет ловить возможные исключения оттуда?


KK>>Я смотрю на это как на потенциальную замаскированную ошибку.


EP>Контр-пример — как ловить исключения из конструктора такого объекта?


Для нелокальных объектов — никак. Для локальных объектов — с помощью обычных try и catch.

void foo(const wchar_t* filename)
{
    try
    {
        static File file(filename);
    }
    catch (/*...*/)
    {
        /*...*/
    }
}

EP>>>Более того, необходимо помнить об транзитивности кидающих деструкторов.
KK>>Если бы была поддержка на уровне компилятора, думаю, эту проблему можно было бы легко разрулить. Типа финализатор неявно вызывал бы финализаторы подобъектов, а деструктор — деструкторы подобъектов.

EP>На счёт того что лучше — сначала всё deferred, а потом все деструкторы, или вперемешку — имхо лучше вперемешку, так как есть сейчас, но я это ещё не рассматривал.


Вызов вперемешку снова отдаляет нас от следования принципу "одна задача — одна функция".

EP>А транзитивность я упомянул в контексте того, что допустим есть некий класс A, он используется по всей программе, никогда ничего из деструктора не кидал, код его использующий на это не расчитан. Теперь если где-то в глубине его композиции добавится член кидающий из деструктора, то сам этот класс A будет иметь кидающий деструктор.


Ну вот и пример того, что потенциально бросающие операции не следует выполнять неявно.
Re[20]: 1) Родовая травма: исключеиния в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 17.10.12 17:54
Оценка:
Здравствуйте, Ku-ku, Вы писали:

KK>>>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?

EP>>По исключениям.
KK>У меня возникают сомнения насчёт того, что этого достаточно.

В смысле? Сейчас из деструктора нельзя кидать когда летит другое исключение, прям на выходе из него — компилятор же знает когда это происходит.
По-крайней мере он может различить когда он из обычного flow, переходит на unwinding и начинает звать деструкторы. Дальше, самое простое решение — передавать в деструктор флаг is_unwinding, и деструкторы передают этот флаг деструкторам вложенных объектов, а потом родителям.

KK>>>Мой поинт в том, что клиент сам должен принимать решение насчёт вызова deferred, а автор класса не должен навязывать какую-то "единственно правильную" стратегию. Например, я могу захотеть выйти из блока через return, не вызывая deferred.

EP>>Ну вот сейчас авторы классов навязывают стратегию — деструкторы тоже вызываются по return.
KK>Только для автоматических переменных. Никто не мешает управлять временем жизни объекта вручную.

точно также с объектами, которые имеют deferred действия.

EP>>А вот в каких случаях не нужно вызывать flush по return, но нужно close — трудно представляю.

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

Если flush кидает, то скорей всего и конструктор кидает, да и write тоже кидает — ловить придётся в любом случае.


EP>>Но суть не в этом — выбор-то всё-равно есть. Автор класса может сделать доступным метод flush, и в private хранить и проверять is_flushed..

KK>Как выполнить только очистку ресурсов?

Если это действительно нужно — об этом может позаботится автор класса на основе того же is_flushed.
Но, вряд ли это нужно — например fclose всегда flush'ит, его нельзя попросить только освободить хэндл.

EP>>Меня волнует больше другой аспект: вот допустим есть контейнер для таких объектов — какое поведение должно быть у деструктора контейнера?

KK>При поддержке со стороны компилятора можно было бы сделать так: финализатор зовёт финализаторы, деструктор зовёт деструкторы.

да, вот только я пока не вижу каких-то явных преимуществ этой группировки.
Может это даже немного хуже — ведь по-идеи, когда flush произошёл, объект можно сразу деструцировать. При группировке, объекты будут жить (и кушать ресурсы) немного дольше.

KK>С комбинированными деструкторами можно поступить вот таким образом


KK>template <typename BidirectionalIterator>

KK>void destroyRange(BidirectionalIterator first, BidirectionalIterator last)
KK>{
KK> while (last != first)
KK> (destroy)(*--last);
KK> SCOPE_EXIT(&) // or SCOPE_FAILURE(&)
KK> {
KK> while (last != first)
KK> (destroy)(*--last);
KK> };
KK>}[/ccode]

О, отличная идея!
Я даже не сразу въехал — действительно, ведь мы исключение никакое не ловим, uncaught_exception_count тем самым не изменяем!
Большое спасибо!
Небольшая поправка — SCOPE_EXIT/SCOPE_FAILUE нужно поставить до первого while иначе оно не вызовется при исключении (точно также как и в языке D).
Это даже можно в макрос завернуть, чтобы убрать дублирование. Я бы наверное предпочёл здесь SCOPE_FAILURE.
То есть новый макрос бы назывался как-то типа DO_NOW_AND_RETRY_FAILURE (только надо придумать что-нибудь более лаконичное). Либо в функцию принимающую лямбду.

KK>>>>>>>Может, ему взбредёт в голову сделать объект статическим или thread_local. Зачем ему тогда flush в деструкторе, и как он будет ловить возможные исключения оттуда?

KK>>>Я смотрю на это как на потенциальную замаскированную ошибку.
EP>>Контр-пример — как ловить исключения из конструктора такого объекта?
KK>Для нелокальных объектов — никак. Для локальных объектов — с помощью обычных try и catch.

Да, и это же не делает нелокальные объекты не-юзабельными?

EP>>>>Более того, необходимо помнить об транзитивности кидающих деструкторов.

KK>>>Если бы была поддержка на уровне компилятора, думаю, эту проблему можно было бы легко разрулить. Типа финализатор неявно вызывал бы финализаторы подобъектов, а деструктор — деструкторы подобъектов.
EP>>На счёт того что лучше — сначала всё deferred, а потом все деструкторы, или вперемешку — имхо лучше вперемешку, так как есть сейчас, но я это ещё не рассматривал.
KK>Вызов вперемешку снова отдаляет нас от следования принципу "одна задача — одна функция".

Представь что это две разные функции — одна вызывается по условию, вторая идёт за ней и без условия.

EP>>А транзитивность я упомянул в контексте того, что допустим есть некий класс A, он используется по всей программе, никогда ничего из деструктора не кидал, код его использующий на это не расчитан. Теперь если где-то в глубине его композиции добавится член кидающий из деструктора, то сам этот класс A будет иметь кидающий деструктор.

KK>Ну вот и пример того, что потенциально бросающие операции не следует выполнять неявно.

Когда не было исключений вообще, код точно также не был не рассчитан на них, как сейчас не рассчитан на кидающие деструкторы.
Re[21]: 1) Родовая травма: исключеиния в деструкторах
От: Ku-ku  
Дата: 17.10.12 20:52
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

KK>>>>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?

EP>>>По исключениям.
KK>>У меня возникают сомнения насчёт того, что этого достаточно.

EP>В смысле? Сейчас из деструктора нельзя кидать когда летит другое исключение


А ещё, например, нельзя кидать из деструктора thread_local объекта. Будем звать оттуда deferred action или не? Вопрос вообще о том, хочет ли клиент вызова deferred action непосредственно до освобождения ресурсов.

EP>>>Ну вот сейчас авторы классов навязывают стратегию — деструкторы тоже вызываются по return.

KK>>Только для автоматических переменных. Никто не мешает управлять временем жизни объекта вручную.

EP>точно также с объектами, которые имеют deferred действия.


Не точно и не так же, если я не могу выполнить освобождение ресурсов без deferred action.

EP>>>А вот в каких случаях не нужно вызывать flush по return, но нужно close — трудно представляю.

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

EP>Если flush кидает, то скорей всего и конструктор кидает, да и write тоже кидает — ловить придётся в любом случае.


Это зависит от интерфейса File. Иногда одни и те же операции реализуют в двух вариантах: с использованием исключений для сообщения об ошибках и с использованием кодов ошибок.

KK>>Как выполнить только очистку ресурсов?


EP>Если это действительно нужно — об этом может позаботится автор класса на основе того же is_flushed.


А поконкретнее? Как это будет выглядеть в клиентском коде?

EP>Но, вряд ли это нужно — например fclose всегда flush'ит, его нельзя попросить только освободить хэндл.


fclose — очень древняя функция. Сомневаюсь, что десятилетия назад её авторы задумывались о таких вещах, как SRP.

EP>>>Меня волнует больше другой аспект: вот допустим есть контейнер для таких объектов — какое поведение должно быть у деструктора контейнера?

KK>>При поддержке со стороны компилятора можно было бы сделать так: финализатор зовёт финализаторы, деструктор зовёт деструкторы.

EP>да, вот только я пока не вижу каких-то явных преимуществ этой группировки.


Получается чёткое разделение обязанностей. Финализаторы и деструкторы не проверяют внутри себя никаких флагов. Проверка нужна только перед вызовом финализатора контейнера, и делать её должен тот, кто собирается его вызывать. Причём ровно один раз.

EP>Может это даже немного хуже — ведь по-идеи, когда flush произошёл, объект можно сразу деструцировать. При группировке, объекты будут жить (и кушать ресурсы) немного дольше.


Совсем немного

EP>Небольшая поправка — SCOPE_EXIT/SCOPE_FAILUE нужно поставить до первого while иначе оно не вызовется при исключении


Согласен, тут я протупил.

EP>>>Контр-пример — как ловить исключения из конструктора такого объекта?

KK>>Для нелокальных объектов — никак. Для локальных объектов — с помощью обычных try и catch.

EP>Да, и это же не делает нелокальные объекты не-юзабельными?


Смотря какие объекты. Я бы испытал некоторый восторг, если бы при невозможности открыть какой-то файл на старте программа падала из-за std::terminate.

KK>>Вызов вперемешку снова отдаляет нас от следования принципу "одна задача — одна функция".


EP>Представь что это две разные функции — одна вызывается по условию, вторая идёт за ней и без условия.


Не получается представить. Деструктор контейнера будет выполнять сначала часть deferred action, потом часть освобождения ресурсов, потом снова часть deferred action, потом снова часть освобождения ресурсов и т.д.
Re[22]: 1) Родовая травма: исключеиния в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 18.10.12 11:05
Оценка:
Здравствуйте, Ku-ku, Вы писали:

KK>>>>>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?

EP>>>>По исключениям.
KK>>>У меня возникают сомнения насчёт того, что этого достаточно.
EP>>В смысле? Сейчас из деструктора нельзя кидать когда летит другое исключение
KK>А ещё, например, нельзя кидать из деструктора thread_local объекта. Будем звать оттуда deferred action или не? Вопрос вообще о том, хочет ли клиент вызова deferred action непосредственно до освобождения ресурсов.

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

EP>>>>Ну вот сейчас авторы классов навязывают стратегию — деструкторы тоже вызываются по return.

KK>>>Только для автоматических переменных. Никто не мешает управлять временем жизни объекта вручную.
EP>>точно также с объектами, которые имеют deferred действия.
KK>Не точно и не так же, если я не могу выполнить освобождение ресурсов без deferred action.

1. Я не уверен что это действительно проблема.
2. fclose флушит всегда
3. Автора класса может добавить интерфейс для отмены deferred
4.
template<typename T>
void delete_without_deferred(const T *const ptr)
{
    try
    {
        scope(failure)
        {
            delete ptr;
        };
        throw 1;
    }
    catch(int){}
}
...
{
    File *f=new File("temp.txt");
    delete_without_deferred(f);
}



EP>>>>А вот в каких случаях не нужно вызывать flush по return, но нужно close — трудно представляю.

KK>>>Например, когда функция не может бросать исключения и должна использовать коды возврата для сообщения об ошибках. Можно конечно бросить исключение и тут же его поймать, после чего сделать return, но с одним return было бы как-то проще.
EP>>Если flush кидает, то скорей всего и конструктор кидает, да и write тоже кидает — ловить придётся в любом случае.
KK>Это зависит от интерфейса File. Иногда одни и те же операции реализуют в двух вариантах: с использованием исключений для сообщения об ошибках и с использованием кодов ошибок.

1. ИМХО, лучше иметь два разных класса — один с исключениями, другой без.
2. Может точно также реализовать класс который позволяет контролировать кидаются исключения из deferred или нет.

KK>>>Как выполнить только очистку ресурсов?

EP>>Если это действительно нужно — об этом может позаботится автор класса на основе того же is_flushed.
KK>А поконкретнее? Как это будет выглядеть в клиентском коде?

например:
{
    SomeWithDeferred obj;
    obj.do_not_flush();
}


EP>>Но, вряд ли это нужно — например fclose всегда flush'ит, его нельзя попросить только освободить хэндл.

KK>fclose — очень древняя функция. Сомневаюсь, что десятилетия назад её авторы задумывались о таких вещах, как SRP.

Я сомневаюсь что SRP полезен во всех случаях.

Вот например есть std::transform и std::accumulate. С помощью них можно вычислить скалярное произведение двух векторов.
А есть std::inner_product, который эти две операции комбинирует. И что, нарушение SRP? Причём злостный SRP нарушитель — inner_product, справляется с задачей намного быстрее и не требует промежуточных буферов.

Другой пример, есть autotools — всякие autoscan, aclocal, autoheader, automake, autoconf, configure, make — каждая тулза SRP'шная. Граф их комбинирования не самый простой (см wiki).
А есть CMake — который нисколько не SRP'шный, но использовать его намного проще

EP>>>>Меня волнует больше другой аспект: вот допустим есть контейнер для таких объектов — какое поведение должно быть у деструктора контейнера?

KK>>>При поддержке со стороны компилятора можно было бы сделать так: финализатор зовёт финализаторы, деструктор зовёт деструкторы.
EP>>да, вот только я пока не вижу каких-то явных преимуществ этой группировки.
KK>Получается чёткое разделение обязанностей. Финализаторы и деструкторы не проверяют внутри себя никаких флагов.

При поддержки со стороны компиляторов комбинированного варианта, деструкторы тоже не будут проверять флаги, по крайней мере явно.

KK>Проверка нужна только перед вызовом финализатора контейнера, и делать её должен тот, кто собирается его вызывать. Причём ровно один раз.

EP>>Может это даже немного хуже — ведь по-идеи, когда flush произошёл, объект можно сразу деструцировать. При группировке, объекты будут жить (и кушать ресурсы) немного дольше.
KK>Совсем немного

Если выбирать между излишним временем жизни объектов и проверками (пусть даже авто-генерированными), я бы выбрал лишние проверки.

EP>>>>Контр-пример — как ловить исключения из конструктора такого объекта?

KK>>>Для нелокальных объектов — никак. Для локальных объектов — с помощью обычных try и catch.
EP>>Да, и это же не делает нелокальные объекты не-юзабельными?
KK>Смотря какие объекты. Я бы испытал некоторый восторг, если бы при невозможности открыть какой-то файл на старте программа падала из-за std::terminate.

Ну так, тем не менее, этот corner-case же не перечёркивает преимущества кидающих конструкторов.

KK>>>Вызов вперемешку снова отдаляет нас от следования принципу "одна задача — одна функция".

EP>>Представь что это две разные функции — одна вызывается по условию, вторая идёт за ней и без условия.
KK>Не получается представить. Деструктор контейнера будет выполнять сначала часть deferred action, потом часть освобождения ресурсов, потом снова часть deferred action, потом снова часть освобождения ресурсов и т.д.

Это не деструктор контейнера будет делать, а комбинированный деструктор объектов.
Кстати, такое выполнение "вперемешку", соответствует тому, что происходит со стековыми объектами.
Конечно, можно и для стэковых реализовать вариант когда сначала все deferred, а потом все release, но тогда:
{
   Some a;
   Some b;
}

и
{
   Some a;
   {
      Some b;
   }
}

Имели бы разный порядок операций, что ИМХО намного хуже и менее интуитивно.
Re[23]: 1) Родовая травма: исключеиния в деструкторах
От: Ku-ku  
Дата: 18.10.12 15:05
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здравствуйте, Ku-ku, Вы писали:


KK>>>>>>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?

EP>>>>>По исключениям.
KK>>>>У меня возникают сомнения насчёт того, что этого достаточно.
EP>>>В смысле? Сейчас из деструктора нельзя кидать когда летит другое исключение
KK>>А ещё, например, нельзя кидать из деструктора thread_local объекта. Будем звать оттуда deferred action или не? Вопрос вообще о том, хочет ли клиент вызова deferred action непосредственно до освобождения ресурсов.

EP>Ну та же самая ситуация с конструкторами глобальных объектов.


Ты не ответил на вопрос. И ситуация с конструкторами совсем не та же самая, так как у меня в распоряжении есть штатные приёмы для откладывания момента инициализации и размещения её там, где я могу поймать все вылетающие исключения. Прямого контроля над уничтожением объекта с static или thread storage duration у меня нет.

EP>Техническая невозможность запретить глобальные объекты с кидающими конструкторами, не делает их неюзабельными в общем случае.


Речь не о невозможности что-то запретить, а о полноте и удобстве контроля над объектом.

KK>>Не точно и не так же, если я не могу выполнить освобождение ресурсов без deferred action.


EP>1. Я не уверен что это действительно проблема.


То, что проблемы нет, тоже не очевидно.

EP>2. fclose флушит всегда


Это просто пример существующего интерфейса, необязательно хорошего.

EP>3. Автора класса может добавить интерфейс для отмены deferred


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

EP>4.

EP>
EP>template<typename T>
EP>void delete_without_deferred(const T *const ptr)
EP>{
EP>    try
EP>    {
EP>        scope(failure)
EP>        {
EP>            delete ptr;
EP>        };
EP>        throw 1;
EP>    }
EP>    catch(int){}
EP>}
EP>...
EP>{
EP>    File *f=new File("temp.txt");
EP>    delete_without_deferred(f);
EP>}

Угу, и ещё такой же шаблон для прямого вызова деструктора. Итого получим усложнение и возможный оверхэд на ровном месте.

EP>>>Если flush кидает, то скорей всего и конструктор кидает, да и write тоже кидает — ловить придётся в любом случае.

KK>>Это зависит от интерфейса File. Иногда одни и те же операции реализуют в двух вариантах: с использованием исключений для сообщения об ошибках и с использованием кодов ошибок.

EP>1. ИМХО, лучше иметь два разных класса — один с исключениями, другой без.


А что делать с общими бессбойными операциями? Дублировать? Не так уж и привлекательно.

EP>2. Может точно также реализовать класс который позволяет контролировать кидаются исключения из deferred или нет.


И какую же версию будет звать деструктор: бросающую или небросающую?

EP>Я сомневаюсь что SRP полезен во всех случаях.


EP>Вот например есть std::transform и std::accumulate. С помощью них можно вычислить скалярное произведение двух векторов.

EP>А есть std::inner_product, который эти две операции комбинирует. И что, нарушение SRP?

Нет. SRP не подразумевает, что задача не может состоять из подзадач.

EP>>>>>Контр-пример — как ловить исключения из конструктора такого объекта?

KK>>>>Для нелокальных объектов — никак. Для локальных объектов — с помощью обычных try и catch.
EP>>>Да, и это же не делает нелокальные объекты не-юзабельными?
KK>>Смотря какие объекты. Я бы испытал некоторый восторг, если бы при невозможности открыть какой-то файл на старте программа падала из-за std::terminate.

EP>Ну так, тем не менее, этот corner-case же не перечёркивает преимущества кидающих конструкторов.


Аналогия с конструкторами неудачная. Конструктор всегда получает управление от видимой в коде конструкции, чего не скажешь о деструкторе.

EP>Кстати, такое выполнение "вперемешку", соответствует тому, что происходит со стековыми объектами.

EP>Конечно, можно и для стэковых реализовать вариант когда сначала все deferred, а потом все release, но тогда:
...
EP>Имели бы разный порядок операций, что ИМХО намного хуже и менее интуитивно.

Мне оба варианта не нравятся, и ломать голову над тем, какой из них хуже, как-то особо не хочется.
Re[24]: 1) Родовая травма: исключеиния в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 18.10.12 20:15
Оценка:
Здравствуйте, Ku-ku, Вы писали:

KK>>>>>>>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?

EP>>>>>>По исключениям.
KK>>>>>У меня возникают сомнения насчёт того, что этого достаточно.
EP>>>>В смысле? Сейчас из деструктора нельзя кидать когда летит другое исключение
KK>>>А ещё, например, нельзя кидать из деструктора thread_local объекта. Будем звать оттуда deferred action или не? Вопрос вообще о том, хочет ли клиент вызова deferred action непосредственно до освобождения ресурсов.
EP>>Ну та же самая ситуация с конструкторами глобальных объектов.
KK>Ты не ответил на вопрос.

На выделенный? Если класс поддерживает deferred — да, будем звать
Точно также как будем кидать исключение из конструктора глобального объекта, если это интерфейс его класса.

KK>И ситуация с конструкторами совсем не та же самая, так как у меня в распоряжении есть штатные приёмы для откладывания момента инициализации и размещения её там, где я могу поймать все вылетающие исключения. Прямого контроля над уничтожением объекта с static или thread storage duration у меня нет.


Например, какие есть штатные приёмы для глобальных объектов? auto_ptr, optional, и т.п.? Ну сделай тоже самое с deferred — явно уничтожай их.

EP>>Техническая невозможность запретить глобальные объекты с кидающими конструкторами, не делает их неюзабельными в общем случае.

KK>Речь не о невозможности что-то запретить, а о полноте и удобстве контроля над объектом.

Ну вот смотри, есть итераторы, есть различные view чего-нибудь, да хоть обычные указатели, то есть это что-то позволяющие взаимодействовать с неким третьим объектом когда он жив, но при этом вызывающие UB/расстрел памяти/segfault если вдруг объект мёртв.
И что, из-за того, что есть всякие corner-case'ы, они же не становятся неюзабельными?

KK>>>Не точно и не так же, если я не могу выполнить освобождение ресурсов без deferred action.

EP>>1. Я не уверен что это действительно проблема.
KK>То, что проблемы нет, тоже не очевидно.
EP>>2. fclose флушит всегда
KK>Это просто пример существующего интерфейса, необязательно хорошего.

Я ни разу не видел возмущения на счёт того, что fclose флушит. А вот обсуждения на тему flush в деструкторе + исключения, идут до сих пор.

KK>Угу, и ещё такой же шаблон для прямого вызова деструктора. Итого получим усложнение и возможный оверхэд на ровном месте.


А ещё тратится место на unwinding_indicator — это же библиотечное решение, а не вшитое в компилятор. При поддержки со стороны компилятора, можно реализовать решение "pay as you go".

EP>>>>Если flush кидает, то скорей всего и конструктор кидает, да и write тоже кидает — ловить придётся в любом случае.

KK>>>Это зависит от интерфейса File. Иногда одни и те же операции реализуют в двух вариантах: с использованием исключений для сообщения об ошибках и с использованием кодов ошибок.
EP>>1. ИМХО, лучше иметь два разных класса — один с исключениями, другой без.
KK>А что делать с общими бессбойными операциями? Дублировать? Не так уж и привлекательно.

Зачем дублировать? Пусть тот который с исключениями, наследует того который без исключений.
Продолжение в том же духе: а что если не только исключения запрещены, но и интерфейс должен быть полностью plain C

EP>>2. Может точно также реализовать класс который позволяет контролировать кидаются исключения из deferred или нет.

KK>И какую же версию будет звать деструктор: бросающую или небросающую?

Версия одна, бросает или нет — зависит от флага. Также как и в ostream — достаточно ведь exceptions установить, а методы остаются теме же.

EP>>Я сомневаюсь что SRP полезен во всех случаях.

EP>>Вот например есть std::transform и std::accumulate. С помощью них можно вычислить скалярное произведение двух векторов.
EP>>А есть std::inner_product, который эти две операции комбинирует. И что, нарушение SRP?
KK>Нет. SRP не подразумевает, что задача не может состоять из подзадач.

Ну так и тут есть задача финализации объекта, которая состоит из подзадач — deferred и release.

EP>>>>>>Контр-пример — как ловить исключения из конструктора такого объекта?

KK>>>>>Для нелокальных объектов — никак. Для локальных объектов — с помощью обычных try и catch.
EP>>>>Да, и это же не делает нелокальные объекты не-юзабельными?
KK>>>Смотря какие объекты. Я бы испытал некоторый восторг, если бы при невозможности открыть какой-то файл на старте программа падала из-за std::terminate.
EP>>Ну так, тем не менее, этот corner-case же не перечёркивает преимущества кидающих конструкторов.
KK>Аналогия с конструкторами неудачная. Конструктор всегда получает управление от видимой в коде конструкции, чего не скажешь о деструкторе.

Ну и что, что её видно? Допустим, опять-таки, есть глобальный объект, даже несколько, в разных TU, порядок вызова этих "видимых" конструкций определён?

EP>>Имели бы разный порядок операций, что ИМХО намного хуже и менее интуитивно.

KK>Мне оба варианта не нравятся, и ломать голову над тем, какой из них хуже, как-то особо не хочется.

А мне свекла не нравится А если серьёзно, то я не увидел сильных аргументов против.
Re[25]: 1) Родовая травма: исключеиния в деструкторах
От: Ku-ku  
Дата: 19.10.12 08:21
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

KK>>>>>>>>Cразу возникает вопрос: а как компилятор будет угадывать, когда надо вызывать deferred и когда не надо?

EP>>>>>>>По исключениям.
KK>>>>>>У меня возникают сомнения насчёт того, что этого достаточно.
EP>>>>>В смысле? Сейчас из деструктора нельзя кидать когда летит другое исключение
KK>>>>А ещё, например, нельзя кидать из деструктора thread_local объекта. Будем звать оттуда deferred action или не? Вопрос вообще о том, хочет ли клиент вызова deferred action непосредственно до освобождения ресурсов.
EP>Если класс поддерживает deferred — да, будем звать

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

KK>>И ситуация с конструкторами совсем не та же самая, так как у меня в распоряжении есть штатные приёмы для откладывания момента инициализации и размещения её там, где я могу поймать все вылетающие исключения. Прямого контроля над уничтожением объекта с static или thread storage duration у меня нет.


EP>Например, какие есть штатные приёмы для глобальных объектов? auto_ptr, optional, и т.п.?


Объекты, чьё время жизни регулируется классами auto_ptr и optional, имеют dynamic storage duration. Я имел в виду создание функции наподобие

File& file()
{
    static File f(getFilename());
    return f;
}

Ещё можно создать объект, не связанный с каким-либо файлом и потом открыть файл через какой-нибудь open.

EP>Ну вот смотри, есть итераторы, есть различные view чего-нибудь, да хоть обычные указатели, то есть это что-то позволяющие взаимодействовать с неким третьим объектом когда он жив, но при этом вызывающие UB/расстрел памяти/segfault если вдруг объект мёртв.

EP>И что, из-за того, что есть всякие corner-case'ы, они же не становятся неюзабельными?

Неюзабельными не становятся. Зато становятся error prone в какой-то степени.

У нас есть два подхода: засовывать в деструкторы отложенные операции, не являющиеся очисткой ресурсов, или не засовывать. Оба подхода можно сравнить на предмет читаемости, предрасположенности к провоцированию совершения ошибок и простоты поддержки. Для меня, с учётом моих субъективных качеств, первый подход вчистую проигрывает второму по пунктам 1 и 3 и как минимум не лучше по пункту 2. Но да, каких-то фатальных недостатков, делающих первый подход абсолютно неюзабельным, я не вижу.

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

void errorHandler()
{
    try
    {
        throw;
    }
    catch (/*...*/)
    {
        /*...*/
    }
}

class File
{
/*...*/
public:
    File(const wchar_t* filename, const std::function<void()>& errorHandler) :
        errorHandler(errorHandler)
    {
        /*...*/
    }
    ~File()
    {
        try
        {
            flush();
        }
        catch (...)
        {
            errorHandler();
        }
        release();
    }
private:
    std::function<void> errorHandler;
};

void foo(const wchar_t* filename)
{
    File(filename, errorHandler);
}

Или вытаскивать наружу исключения не с помощью раскрутки стека, а через передачу наверх std::exception_ptr

class File
{
/*...*/
public:
    File(const wchar_t* filename, std::exception_ptr& exception) :
        exception(exception)
    {
        /*...*/
    }
    ~File()
    {
        try
        {
            flush();
        }
        catch (...)
        {
            exception = std::current_exception();
        }
        release();
    }
private:
    std::exception_ptr& exception;
};

void foo(const wchar_t* filename, std::exception_ptr& exception)
{
    File(filename, exception);
}


EP>>>1. ИМХО, лучше иметь два разных класса — один с исключениями, другой без.

KK>>А что делать с общими бессбойными операциями? Дублировать? Не так уж и привлекательно.

EP>Зачем дублировать? Пусть тот который с исключениями, наследует того который без исключений.


Не пойму в чём прелесть такого финта ушами.

EP>Продолжение в том же духе: а что если не только исключения запрещены, но и интерфейс должен быть полностью plain C


Тогда дублирование оправдано, поскольку пользоваться C++ интерфейсом там, где это возможно, гораздо удобнее, чем сишным.

EP>>>2. Может точно также реализовать класс который позволяет контролировать кидаются исключения из deferred или нет.

KK>>И какую же версию будет звать деструктор: бросающую или небросающую?

EP>Версия одна, бросает или нет — зависит от флага.


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

EP>Также как и в ostream — достаточно ведь exceptions установить, а методы остаются теме же.


ostream — это настоящая помойка и пример того, как делать не нужно.

KK>>Нет. SRP не подразумевает, что задача не может состоять из подзадач.


EP>Ну так и тут есть задача финализации объекта, которая состоит из подзадач — deferred и release.


Финализация объекта — это не задача. Если бы произвольную сумму действий можно было назвать задачей, то SRP не имел бы смысла.

KK>>Аналогия с конструкторами неудачная. Конструктор всегда получает управление от видимой в коде конструкции, чего не скажешь о деструкторе.


EP>Ну и что, что её видно? Допустим, опять-таки, есть глобальный объект, даже несколько, в разных TU, порядок вызова этих "видимых" конструкций определён?


У меня нет привычки сначала добавлять в программу глобальные переменные, а потом уже думать, как их надо инициализировать. Если меня по каким-то причинам не устраивают возможные способы инициализации глобальной переменной, то вместо глобальной переменной я буду использовать что-то другое. Вопрос в том, насколько сложным будет это "что-то другое". Отсутствие необходимости заботиться о точном времени уничтожения объекта делает мой выбор более свободным.

EP>А мне свекла не нравится А если серьёзно, то я не увидел сильных аргументов против.


Ну значит пора сворачить дискуссию. Пусть каждый останется при своём мнении.
Re[26]: 1) Родовая травма: исключеиния в деструкторах
От: Evgeny.Panasyuk Россия  
Дата: 19.10.12 10:39
Оценка:
Здравствуйте, Ku-ku, Вы писали:

KK>Ну то есть безопасность вызова не является критерием и ориентируемся мы исключительно на то, вызван ли деструктор из-за раскрутки стека. Позиция понятна.


да, именно так. deferred — своего рода scope(success) выполняемый перед деструктором.

KK>>>И ситуация с конструкторами совсем не та же самая, так как у меня в распоряжении есть штатные приёмы для откладывания момента инициализации и размещения её там, где я могу поймать все вылетающие исключения. Прямого контроля над уничтожением объекта с static или thread storage duration у меня нет.

EP>>Например, какие есть штатные приёмы для глобальных объектов? auto_ptr, optional, и т.п.?
KK>Объекты, чьё время жизни регулируется классами auto_ptr и optional, имеют dynamic storage duration. Я имел в виду создание функции наподобие
KK>
File& file()
KK>{
KK>    static File f(getFilename());
KK>    return f;
KK>}


трик известный, вот только вопрос был про глобальный объект.

KK>Ещё можно создать объект, не связанный с каким-либо файлом и потом открыть файл через какой-нибудь open.


то есть это вариант с некидающим конструктором, и явным вызовом кидающего действия? можно также явно вызывать deferred..

EP>>Ну вот смотри, есть итераторы, есть различные view чего-нибудь, да хоть обычные указатели, то есть это что-то позволяющие взаимодействовать с неким третьим объектом когда он жив, но при этом вызывающие UB/расстрел памяти/segfault если вдруг объект мёртв.

EP>>И что, из-за того, что есть всякие corner-case'ы, они же не становятся неюзабельными?
KK>Неюзабельными не становятся. Зато становятся error prone в какой-то степени.

Согласен, error-prone момент присутствует. Также как и у кидающих конструкторов глобальных объектов.

KK>У нас есть два подхода: засовывать в деструкторы отложенные операции, не являющиеся очисткой ресурсов, или не засовывать.


Отложенные операции засовываются в деструкторы и сейчас. Выполняются всегда. Иногда фейлятся, иногда нет. Если фейлятся, то исключения глотаются + максимум скромная запись в log.

KK>Оба подхода можно сравнить на предмет читаемости, предрасположенности к провоцированию совершения ошибок и простоты поддержки. Для меня, с учётом моих субъективных качеств, первый подход вчистую проигрывает второму по пунктам 1 и 3 и как минимум не лучше по пункту 2.


ИМХО, когда исключения только появились, они тоже сливали по пунктам 1 и 3 коду без них, как минимум потому, что далеко не все сразу научились ими пользоваться (к сожалению, многие не умеют это делать и по сей день).

KK>Но да, каких-то фатальных недостатков, делающих первый подход абсолютно неюзабельным, я не вижу.



Фатальных недостатков я тоже не вижу. Мне интересно узнать, в первую очередь, именно о них.

KK>Кстати, с исключениями можно было бы и по-другому извратиться. Например, обрабатывать исключения прямо в деструкторе с помощью заранее сохранённого в объекте обработчика исключений.

KK>
void errorHandler()
...
KK>void foo(const wchar_t* filename)
KK>{
KK>    File(filename, errorHandler);
KK>}

KK>Или вытаскивать наружу исключения не с помощью раскрутки стека, а через передачу наверх std::exception_ptr
KK>void foo(const wchar_t* filename, std::exception_ptr& exception)
KK>{
KK> File(filename, exception);
KK>}[/ccode]

То есть появляется два параллельных механизма обработки "исключений" (обычный и errorHandler/exception_ptr), работающих по разному, каждое из которых требует разных действий со стороны пользоватля? Причём вариант с явным errorHandler/exception_ptr требует лишних действий на всём пути от throw до catch.

EP>>>>1. ИМХО, лучше иметь два разных класса — один с исключениями, другой без.

KK>>>А что делать с общими бессбойными операциями? Дублировать? Не так уж и привлекательно.
EP>>Зачем дублировать? Пусть тот который с исключениями, наследует того который без исключений.
KK>Не пойму в чём прелесть такого финта ушами.

это ответ на вопрос про дублирование.

EP>>>>2. Может точно также реализовать класс который позволяет контролировать кидаются исключения из deferred или нет.

KK>>>И какую же версию будет звать деструктор: бросающую или небросающую?
EP>>Версия одна, бросает или нет — зависит от флага.
KK>Ну то есть при наличии этих наворотов забот у программиста меньше не становится, теперь он должен помнить о дополнительном состоянии объекта.

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

EP>>Также как и в ostream — достаточно ведь exceptions установить, а методы остаются теме же.

KK>ostream — это настоящая помойка и пример того, как делать не нужно.

Ну так я согласен. Это же не я предложил класс поддерживающий два варианта обработки ошибок

KK>Это зависит от интерфейса File. Иногда одни и те же операции реализуют в двух вариантах: с использованием исключений для сообщения об ошибках и с использованием кодов ошибок.

ostream делает именно это.

KK>>>Нет. SRP не подразумевает, что задача не может состоять из подзадач.

EP>>Ну так и тут есть задача финализации объекта, которая состоит из подзадач — deferred и release.
KK>Финализация объекта — это не задача. Если бы произвольную сумму действий можно было назвать задачей, то SRP не имел бы смысла.

При поддержки со стороны компилятора, когда deferred вызывается не из деструктора — SRP нарушается?

EP>>А мне свекла не нравится А если серьёзно, то я не увидел сильных аргументов против.

KK>Ну значит пора сворачить дискуссию. Пусть каждый останется при своём мнении.

Ну так ты же тоже согласен, что каких-то фатальных недостатоков нет.
Не фатальные — есть (хотя и не маловажные, и несомненно требующие внимания).
Re[10]: 1) Родовая травма: исключеиния в деструкторах
От: enji  
Дата: 19.10.12 14:54
Оценка:
Здравствуйте, koandrew, Вы писали:

K>Здравствуйте, enji, Вы писали:


E>> Ну дык если надо что-то неубиваемое — создаем отдельный вотчдог, который перезапускает рабочий процесс, если тот перестал подавать признаки жизни.


K>Ага, а для контроля этого вотчдога — ещё один вотчдог Видел я такие конструкции в продакшене — довольно унылое зрелище...


А для контроля вотчдога — аппаратный вотчдог, который перезагружает камп
Или несколько компов, которые обмениваются результатами, и если у кого-то результат неверный, его перезапускают. Схем много...

В простом случае одного вотчдога хватает — обычно вотчдог очень прост и мы полагаем, что зависнуть он не может Так например firebird работает.
Re[2]: C++ и RAII
От: dr.Chaos Россия Украшения HandMade
Дата: 19.10.12 17:56
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Что-то, Егор, я не улавливаю сути вопроса. Ты хочешь, что бы мы совместными усилиями провели четкую границу применимости RAII?


Нет он просто проникся монадой Maibe и понял что обработка ошибок с помощью исключений ущербна. Только ФВП, только продолжения, только хардкор.
Побеждающий других — силен,
Побеждающий себя — Могущественен.
Лао Цзы
Re[2]: C++ и RAII
От: dr.Chaos Россия Украшения HandMade
Дата: 19.10.12 18:01
Оценка:
Здравствуйте, andyp, Вы писали:

A>2. Когда попадаем в деструктор не совсем ясно, то ли мы раскручаем стек из-за исключения, связанного с нашим ресурсом, то ли все нормально (ну те нет исключения), то ли исключение вызвано чем-то другим (не нашим ресурсом). Конечно в с++11 есть current_exception(), но проверять и отлавливать свои исключения...

И добавьте сюда, что у некоторых компиляторов unhandle_exception не потокобеопасный.
Побеждающий других — силен,
Побеждающий себя — Могущественен.
Лао Цзы
Re[27]: 1) Родовая травма: исключеиния в деструкторах
От: Ku-ku  
Дата: 19.10.12 20:40
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>трик известный, вот только вопрос был про глобальный объект.


Так мне вроде никто не запрещал вместо глобального объекта использовать что-нибудь другое

EP>>>Ну вот смотри, есть итераторы, есть различные view чего-нибудь, да хоть обычные указатели, то есть это что-то позволяющие взаимодействовать с неким третьим объектом когда он жив, но при этом вызывающие UB/расстрел памяти/segfault если вдруг объект мёртв.

EP>>>И что, из-за того, что есть всякие corner-case'ы, они же не становятся неюзабельными?
KK>>Неюзабельными не становятся. Зато становятся error prone в какой-то степени.
EP>Согласен, error-prone момент присутствует. Также как и у кидающих конструкторов глобальных объектов.

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

KK>>У нас есть два подхода: засовывать в деструкторы отложенные операции, не являющиеся очисткой ресурсов, или не засовывать.

EP>Отложенные операции засовываются в деструкторы и сейчас. Выполняются всегда. Иногда фейлятся, иногда нет. Если фейлятся, то исключения глотаются + максимум скромная запись в log.

Я так не делаю. Ну может только за исключением обёрток над каким-нибудь кривым API.

KK>>Оба подхода можно сравнить на предмет читаемости, предрасположенности к провоцированию совершения ошибок и простоты поддержки. Для меня, с учётом моих субъективных качеств, первый подход вчистую проигрывает второму по пунктам 1 и 3 и как минимум не лучше по пункту 2.

EP>ИМХО, когда исключения только появились, они тоже сливали по пунктам 1 и 3 коду без них, как минимум потому, что далеко не все сразу научились ими пользоваться (к сожалению, многие не умеют это делать и по сей день).

Ты веришь, что твою любимую методику ждёт неминуемый успех? Что ж, мечтать не вредно

EP>>>>>1. ИМХО, лучше иметь два разных класса — один с исключениями, другой без.

KK>>>>А что делать с общими бессбойными операциями? Дублировать? Не так уж и привлекательно.
EP>>>Зачем дублировать? Пусть тот который с исключениями, наследует того который без исключений.
KK>>Не пойму в чём прелесть такого финта ушами.
EP>это ответ на вопрос про дублирование.

Я так и не понял зачем надо два класса вместо одного.

KK>>ostream — это настоящая помойка и пример того, как делать не нужно.

EP>Ну так я согласен. Это же не я предложил класс поддерживающий два варианта обработки ошибок

Использование флагов — это не единственный способ. Можно предоставить две функции, как например делается в случае basic_stream_socket::connect из Boost.

KK>>Это зависит от интерфейса File. Иногда одни и те же операции реализуют в двух вариантах: с использованием исключений для сообщения об ошибках и с использованием кодов ошибок.

EP>ostream делает именно это.

Но через заднее место.

KK>>>>Нет. SRP не подразумевает, что задача не может состоять из подзадач.

EP>>>Ну так и тут есть задача финализации объекта, которая состоит из подзадач — deferred и release.
KK>>Финализация объекта — это не задача. Если бы произвольную сумму действий можно было назвать задачей, то SRP не имел бы смысла.
EP>При поддержки со стороны компилятора, когда deferred вызывается не из деструктора — SRP нарушается?

Вроде, не нарушается. AFAIK, SRP распространяется на функции, классы, модули. А тут к чему его применять?

EP>>>А мне свекла не нравится А если серьёзно, то я не увидел сильных аргументов против.

KK>>Ну значит пора сворачить дискуссию. Пусть каждый останется при своём мнении.
EP>Ну так ты же тоже согласен, что каких-то фатальных недостатоков нет.

У ручного управления ресурсами, например, их тоже нет. Но это как-то не очень мотивирует к применению такого подхода при возможности использовать более удобную альтернативу вроде RAII.
Re[2]: 1) Родовая травма: исключеиния в деструкторах
От: Ночной Смотрящий Россия  
Дата: 19.10.12 21:26
Оценка: +1 :)
Здравствуйте, Erop, Вы писали:

E>Конструктор от имени файла будет у нас документ создавать.

E>Конструктор по умолчанию, будет создавать пустой документ
E>Деструктор будет записывать изменения, если они были.

E>Вот тут нас и ждёт засада. Предположим, что в деструкторе что-то пошло не так, файл оказался недоступен по записи, или его вообще ещё не назначили этому документу, или мы вообще решили перед записью уточнять надо ли записать изменения.

E>Вот тут-то у нас и возникнет проблема. Во-первых, нам придётся всё это спросить не выходя из деструктора, то есть, засунуть в данные кусок интерфейса, либо нагородить каких-то колбэков.
E>Ну это ещё пол-беды. Главная родовая травма деструкторов С++, это то, что их нельзя тормознуть.
E>То есть, если пользователь в алерт-боксе, нажмёт не "Сохранить" или "не сохранять", а "отменить", то мы вообще не сможем никак этого поддержать в нашей прекрасной RAII-архитектуре

Тут проблема не в RAII, а в грубейшем нарушении SRP.
Re[3]: C++ и RAII
От: Ночной Смотрящий Россия  
Дата: 19.10.12 21:30
Оценка:
Здравствуйте, Erop, Вы писали:

E>Просто меня тут поддостал кое-кто из боссов


А с чего ты решил, что это кто то конкретный? Я так проглядел выборочно последние твои баны — вроде везде по делу. Может проблемы в себе поискать?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.