LINUX.ORG.RU

Деструкторы не нужны (?)

 


0

5

Тут в соседней теме один анон сказанул следующее:

Дело не в том. Сишка, она про написание руками + можно навесит абстракций, аки глиб/гобджект. Кресты же — изначально нагромождение абстракций со строками, срущими в кучу, функциональными объектами, методами, вызывающимися неявно (например, деструкторы, на которые жаловался кармак) и проч

Собственно, хотелось бы поговорить о выделенном.

Антон прикрылся ссылкой, по которой про деструкторы я так ничего и не нашёл. Более того, в твиттере Кармака всё выглядит с точностью до наоборот — https://twitter.com/id_aa_carmack/status/172340532419375104

RAII constructor / destructor pairing and templates for type safe collections are pretty high on my list of C++ benefits over C

Кто прав? Кто виноват? И нужны ли в итоге C++ деструкторы?

Ответ на: комментарий от anonymous

В Си нет исключений.

Зато есть longjmp, он стандарт и его можно встретить в популярных библиотеках и программах. Например, в libjpeg обработка ошибок через него сделана. А вот «__attribute__ ((__cleanup__(free)))» мне не попадались.

anonymous
()
Ответ на: комментарий от anonymous

В Си нет исключений.

Это не означает, что в C нет ограничений на время жизни переменных (таких же, как и в C++). Какой-нибудь логгер в C-ной программе все равно будет где-то определен. Или какой-нибудь вектор для хранения ошибок. И времена жизни этих сущностей могут закончится раньше, чем мы доберемся до логического уровня, на котором что-то с ошибками можно сделать.

С++ - скорее набор инструментов, плохо между собой согласующихся.

Как бы то ни было, это не повод выбрасывать те из них, которые упрощают жизнь разработчику. В частности, деструкторы и RAII.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Это будет видно в нашем коде явно. В отличие ситуации с исключениями.

PS Кстати, в Swift'е вроде неплохо сделали, но я с ним не работал.

anonymous
()
Ответ на: комментарий от eao197

Вы не показали кода, так что нет оснований верить вам на слово.

При чем тут вера? Исключения летят неявно, коды возврата - всегда прописаны в коде.

anonymous
()
Ответ на: комментарий от anonymous

При чем тут вера?

Хочется верить, что человек понимает, что говорит сам и что говорят ему в ответ. Но пока оснований для этого нет.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Пусть будет код, аналогичный вашему

void close_file(FILE * f, error_collector * collector)
{
    if (EOF == fclose(f)) {
        error_collector_push(collector, ...);
    }
}

int foo(error_collector * collector) {
  FILE * f1 = NULL, f2 = NULL, f3 = NULL;
  int rc = -1;
  if( !(f1 = fopen(...)) ) goto end;
  if( !(f2 = fopen(...)) ) goto close_f1;
  if( !(f3 = fopen(...)) ) goto close_f2;
...
  if( -1 == some_other_op() ) goto cleanup;
...
cleanup:
close_f3:
  close_file(f3, collector);
close_f2:
  close_file(f2, collector);
close_f1:
  close_file(f1, collector);
end:
  return rc;
}

anonymous
()
Ответ на: комментарий от anonymous

void close_file(FILE * f, error_collector * collector)

Но если collector лежит/доступен только по стеку ближе к нам, чем обработчик исключения, то мы таки потеряем данные. В этом и заключается проблема C++, которая в этой теме поднимается...

Если error_collector лежит/доступен только по стеку ближе к нам, чем обработчик кодов возата, то мы таки потеряем данные. В этом и заключается проблема C, которая в этой теме всплыла...

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Если error_collector лежит/доступен только по стеку ближе к нам, чем обработчик кодов возата, то мы таки потеряем данные.

В коде это будет как-то так:

int bar()
{
    error_collector collector;
    ...
    if (foo(&collector) == -1) {
        // <-- очевидно забыли про collector
        return -1;
    }
    ...
}

А на C++ с исключениями это вполне может быть так:

int bar()
{
    error_collector collector;
    ...
    foo(&collector);
    ...
}
Тут нет ни явного возврата в случае ошибки, ни проверки, ничего. Совершенно неочевидно, что мы в этом месте можем что-то потерять.

anonymous
()
Ответ на: комментарий от eao197

Речь о том, что для C++ error_collector штука, поставленная сбоку от стандартной схемы обработки ошибок - исключений. И вообще не очень хорошо с ними дружит. В си же никакой стандартной схемы нет. В каждом проекте - свой подход. И error_collector просто одно из возможных решений.

anonymous
()
Ответ на: комментарий от eao197

Вам бы со своими вопросами веры сперва разобраться нужно.

Что, скажите, что не так происходит в проекте с исключениями? Неужели вы каждую функцию обкладываете try catch?

anonymous
()
Ответ на: комментарий от anonymous

Не суть важно, обкладываю ли я вызовы try/catch. Важно, что в контексте данного разговора вы искренне верите в то, что вот здесь:

int bar()
{
    error_collector collector;
    ...
    if (foo(&collector) == -1) {
        // <-- очевидно забыли про collector
        return -1;
    }
    ...
}
потеря error_collector-а заметнее, чем вот здесь:
int bar()
{
    error_collector collector;
    ...
    foo(&collector);
    ...
}
И это именно что вопрос веры, так как объективных аргументов у вас нет.

Ну и для защиты от таких ситуаций в C++ можно сделать что-то вроде:

class error_collector {
  bool handled_{false};
  ...
public :
  ~error_collector() noexcept(false) {
    if(!handled_) throw std::runtime_error(...);
  }
  template<typename LAMBDA> handle(LAMBDA l) {
    handled_ = true;
    l(*this);
  }
};
И тогда вы точно получите по рукам, если забудете обработать список ошибок.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ну и для защиты от таких ситуаций в C++ можно сделать что-то вроде

Ай-яй-яй, а как же исключение в деструкторе? И если это произойдет в процессе раскрутки стека от исключения, то что мы получим?

anonymous
()
Ответ на: комментарий от eao197

потеря error_collector-а заметнее, чем вот здесь

А вы не согласны? Там ведь и более сложный код может быть. Вы и правда думаете, что проверка резульата + return менее/столь же заметны, что и просто вызов одной из функций?

anonymous
()
Ответ на: комментарий от eao197
  template<typename LAMBDA> handle(LAMBDA l) {
    handled_ = true;
    l(*this);
  }

Не сразу заметил. Вот и костыли пошли в ход... Хорошо еще, что до макросов дело не дошло... Для элементарной задачи.

anonymous
()
Ответ на: комментарий от anonymous

И если это произойдет в процессе раскрутки стека от исключения, то что мы получим?

Получим действие принципа fail fast: не обработали накопившиеся проблемы, значит что-то пошло не так и продолжать работу опаснее, чем прерваться прямо сейчас.

А вы не согласны?

Нет.

Вы и правда думаете, что проверка резульата + return менее/столь же заметны, что и просто вызов одной из функций?

Я думаю, что продолжаю говорить с человеком, который сам не понимает о чем ведет речь. Мы о том, что объект error_collector со всем его содержимым легко потерять. И в случае с if-ом потерять даже проще, т.к. наличие if-а и return-а в нем создает впечатление, что ситуация была должным образом обработана.

Вот и костыли пошли в ход...

С каких пор лямбды стали костылями?

Для элементарной задачи.

Ну посмотрите, что вы сами накостылили для «элементарной» задачи. Прям образец стиля, просты и надежности.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

С каких пор лямбды стали костылями?

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

Мы о том, что объект error_collector со всем его содержимым легко потерять. И в случае с if-ом потерять даже проще, т.к. наличие if-а и return-а в нем создает впечатление, что ситуация была должным образом обработана.

Нет, не создаст. Т.к. в нем нет никакой обработки collector'а. В случае же исключений при чтении кода тяжело сказать, что функция может выкинуть исключение.

Ну посмотрите, что вы сами накостылили для «элементарной» задачи. Прям образец стиля, просты и надежности.

Это калька с вашего кода.

anonymous
()
Ответ на: комментарий от anonymous

Александреску изобрёл банальный Either l r и настолько офигел от своей крутости, что запилил об этом talk. Причём в презентации он сравнивает Expected<T> с Maybe t и говорит, что у Maybe t есть недостаток: там просто Nothing, а не сообщение об ошибке. Почему он не сравнивает с Either, непонятно. Видимо, Maybe — это всё, что он знает.

Ждём, когда Александреску увидит это https://isocpp.org/blog/2014/02/c17-i-see-a-monad-in-your-future, осознает, что Expected<T> можно chain-ить и толкнёт ещё один «инновационный» talk об Expected<T>.

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Александреску изобрёл банальный Either l r

Вся фишка в том, что в C++ это сочетается с std::exception_ptr, т.е. дает возможность в динамике собирать любые исключения и сохранять информацию о них. Так же благодаря выбросу сохраненного исключения при попытке доступа к «ошибочному» результату мы не теряем преимущества обычных исключений.

anonymous
()
Ответ на: комментарий от anonymous

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

Ну и чем вот это:

collector.handle([](auto & c){...})
принципиально отличается вот от этого:
transform(begin(s), end(s), back_inserter(d), [](auto & c){...})
?

Нет, не создаст. Т.к. в нем нет никакой обработки collector'а.

Тут последние 50 сообщений идет речь о том, что проглатывание исключений в деструкторах вам не нравится и вы хотите иметь список ошибок. И к чему в итоге приходим? К тому, что потеря этого самого списка ошибок проблем не создает? Афигеть, дайте два.

Это калька с вашего кода.

Так своего кода вы не написали.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

terminate'ом? Здорово.

А какие еще варианты, когда явно видно, что программа не делает того, что должна? В нативном-то C++?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

И к чему в итоге приходим? К тому, что потеря этого самого списка ошибок проблем не создает? Афигеть, дайте два.

Потеря контекста разговора? Напомню:

наличие if-а и return-а в нем создает впечатление...

Нет, не создаст. Т.к. ...

anonymous
()
Ответ на: комментарий от eao197

Так своего кода вы не написали.

Извечная проблема холиваров Си и крестов. На си вы каждый проект пишите под задачи. Синтетические примеры тяжело писать.

anonymous
()
Ответ на: комментарий от anonymous

А как же исключения? А, реализация их в C++ так сделать не позволяет? Печаль.

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

И вы предлагаете об этом сигнализировать исключением? Ну-ну, верной дорогой.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

Потеря контекста разговора? Напомню:

Вы лучше объективные аргументы найдите, чтобы показать, что вот в такой записи:

error_collector collector;
if(-1 == foo(&collector)) {
  return -1;
}
будет легко обнаружить, что программист обработку содержимого collector забыл написать.

Если сможете. А то с кодом-то у вас напряг.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

И вы предлагаете об этом сигнализировать исключением?

Нет, я предлагаю обработать ошибку. В C++ для этого есть исключения. Но они тут не подходят(в том виде, в котором они есть в этом языке).

Вы же предлагаете просто упасть. Замечательный подход.

anonymous
()
Ответ на: комментарий от eao197

объективные аргументы
легко обнаружить

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

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от anonymous

Нет, я предлагаю обработать ошибку. В C++ для этого есть исключения. Но они тут не подходят(в том виде, в котором они есть в этом языке).

Здесь вообще-то речь идет именно об этом языке. Влажно мечтать об исключениях из Java или CL применительно к C++ — это мощно, конечно, но я оставлю это анонимным экспертам.

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

Вы же предлагаете просто упасть. Замечательный подход.

Да, если программа работает не так, как задумано, лучше упасть. Fail fast как он есть.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

Ну значит таки вопрос веры.

Наличие return-а ничего не скажет о том, что предполагалось сделать, но чего сделано не было.

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

При этом писателям библиотек необходимо в документации обозначать пользователям, в каком случае может выпрыгнуть то или иное исключение, в том числе, исключения всех зависимостей и их зависимостей и их ... зависимостей (если не обрабатывать и не транслировать их в исключения библиотеки) :-) Иначе бедный пользователь будет либо «брать за правило, что может быть любое исключение», махая рукой на обработку, либо будет собирается с духом и вникать в детали реализации всех методов библиотеки и её зависимостей и их зависимостей и ... зависимостей, чтобы выяснить полный перечень возможных исключений и вызове метода :-)

anonymous
()

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

anonymous
()
Ответ на: комментарий от anonymous

Интерфейс для оного есть ещё со времён C++11

Только он ненужен

anonymous
()

Деструкторы не нужны (?)

Нет!

anonymous
()
Ответ на: комментарий от anonymous

В данной теме вы и так уже чрезвычайно много набредили. Но вот этим опусом про «полный перечень возможных исключений» просто пробили дно.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

Ошибки, о которых сигналят этими исключениями, обрабатывать кто будет? И не те, о которых в идеале автор библиотеки указал в документации, а которые вылазят из зависимостей этой библиотеки, например? И вылезут они у тебя в рантайме, в виде не очень красивого сообщения, на которое пользователь обычно очень плохо реагирует.

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

uuwaan ★★
()
Ответ на: комментарий от eao197

В данной теме вы и так уже чрезвычайно много набредили.

Просто пришлось поговорить что такое «универсальный RAII на деструкторах в C++» :-) Тема, конечно, бредовая, но куда деваться, деструкторы же исключения бросать не могут, поэтому и бредят всякими флажками, коллекторами и прочим... :-)

Но вот этим опусом про «полный перечень возможных исключений» просто пробили дно.

Так и скажи, что все возможные исключения сторонних библиотек ты просто не обрабатываешь :-) Или скажи, что в своих библиотеках, если ты пишешь их, возможные исключения своих библиотек и их зависимостей ты не указываешь в документации :-) (Ведь везде может случиться исключение, вот вам catch(...).) :-) Или скажи, что библиотеки, которые используешь, не документируют все возможные исключения, и поэтому в main() ты пишешь { try { run(); } catch (...) { std::clog << «game over\n»; } } :-) Впрочем, почему-то я не удивлён :-)

anonymous
()
Ответ на: комментарий от anonymous

деструкторы же исключения бросать не могут

Могут.

anonymous
()
Ответ на: комментарий от anonymous

Так и скажи, что все возможные исключения сторонних библиотек ты просто не обрабатываешь :-) Или скажи, что в своих библиотеках, если ты пишешь их, возможные исключения своих библиотек и их зависимостей ты не указываешь в документации :-) (Ведь везде может случиться исключение, вот вам catch(...).) :-) Или скажи, что библиотеки, которые используешь, не документируют все возможные исключения, и поэтому в main() ты пишешь { try { run(); } catch (...) { std::clog << «game over\n»; } }

Замените здесь «все возможные исключения» на «все возможные ошибки» и тупость вашего потока сознания может стать очевидна даже вам.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Замените здесь «все возможные исключения» на «все возможные ошибки» и тупость вашего потока сознания может стать очевидна даже вам.

Даже если своей гениальной фразой ты хотел сказать «все возможные логические ошибки», то это вряд ли отменит catch(...) в main() :-) Дорогой эксперт, ошибки времени выполнения в каноничной C++ программе тоже прилетают в виде исключений :-) Но ведь это ни сколько не отменяет необходимости знать о всех возможных ошибках времени выполнения, которые могут прилететь в виде исключений, при работе с библиотекой, у которой может быть куча зависимостей, у которых могут быть кучи зависимостей :-) Вот и изучай, какое из исключений прилетит к тебе из какой-нибудь зависимости :-) А то можно подумать, что с исключениями можно писать код как в учебниках без обработки ошибок, сравнивая его с сишным кодом, где такая обработка ведётся, хвастаясь что на C++ получается короче чем на C :-)

anonymous
()
Ответ на: комментарий от anonymous

Но ведь это ни сколько не отменяет необходимости знать о всех возможных ошибках времени выполнения

Ожидаемо не поняли. Чем больше приложение, тем меньше шансов узнать о всех возможных ошибках времени выполнения, вне зависимости от спобов информирования о них (будь то исключения или коды возврата).

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Чем больше приложение, тем меньше шансов узнать о всех возможных ошибках времени выполнения, вне зависимости от спобов информирования о них (будь то исключения или коды возврата)

Но ведь в случае кода возврата ошибка проверяется в if'ах. И делается return с какой-то конвертацией кода ошибки и пр.

anonymous
()
Ответ на: комментарий от anonymous

Но ведь в случае кода возврата ошибка проверяется в if'ах. И делается return с какой-то конвертацией кода ошибки и пр.

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

int foo() {
  ...
  if(0 != (rc=do_action_1())) return rc;
  if(0 != (rc=do_action_2())) return rc;
  if(0 != (rc=do_action_3())) return rc;
  ...
}
Либо так:
int foo() {
  ...
  if(0 != (rc=do_action_1())) goto cleanup;
  if(0 != (rc=do_action_2())) goto cleanup;
  if(0 != (rc=do_action_3())) goto cleanup;
  ...
cleanup:
  ...
  return rc;
}
И только в некоторых случаях проверяется код ошибки на предмет одного из возможных значений, когда понятно, что можно сделать. Например, когда проверяется EAGAIN/EWOULDBLOCK и т.п.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

А когда не понятно что нужно делать, то код/объект ошибки возвращается вызывающему коду :-) В итоге, при вызове любой функции источник ошибки всегда ровно один - возвращаемое значение, в отличии от исключений, которые могут прилетать из 100500 мест :-)

Можно было сделать обязательным наследоваться от общего класса исключений, сделав его универсальным для хранения любой информации об ошибках :-) С этим можно было бы работать куда более предсказуемо :-) Но нет, давайте сделаем возможным throw «stupid» или throw 1, которыми хоть и никто не будет пользоваться, но ведь это так круто, даже если лишить адекватных людей гарантий и удобного способа работы с исключениями :-)

anonymous
()

Нашёл в Гугле кусочек мудрости по теме кидающихся исключениями деструкторов и обработки ошибок. Тексту ни много ни мало 12 лет, но лично я просветлился.

uuwaan ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.