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++ деструкторы?

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

пример с дверью не очень понял.

Пример с дверью показывает то, что try-with-resources молча давит исключения. И если ты его где-то используешь и об этом ни слухом ни духом, то у меня для тебя плохие новости.

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

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

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

самое главное - не теряется контект возникновения исключений. для того же классического try-finally без suppressed exception list он терялся, если руками не поддерживать список (= спагетти код). хотя и список этот нужен довольно редко.

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

В вполне логичном предположении, что shared_ptr не кидает исключений, если объект не кидает исключений, то изменений в удалении по ключу особых не потребуется:

void erase_by_a(const std::string & key) {
  auto it = index_a_.find(key);
  if(it != index_a_.end()) {
    auto obj_ptr = it->second;
    index_b_.erase(obj_ptr->b_);
    index_a_.erase(it);
  }
}

Удаление по списку ключей в том виде, как вы написали, я бы вообще в интерфейс не стал выносить. Пользователь сам в состоянии написать for и сделать так, как ему надо. В мультииндексе операции удаления по списку ключей вроде бы нет.

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

не давит он, а складывает в suppressed exception list

Название говорит само за себя.

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

Т.е. ты его никогда не проверял?

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

Если так рассуждать, то и в С++ можно безопасно кидать исключения из деструктора:

~imbad() noexcept(false) {
    if( !uncaught_exception() )
        throw "Hi!";
}

И можно так же завести список с исключениями. Но это такой же костыль как и noexcept по дефолту. И если дефолтный noexcept заставляет тебя тут же самостоятельно крепить дверь на место или решать кому это поручить, то try-with-resources даже не скажет тебе, что дверь отвалилась. Ты должен помнить, что надо обязательно выписать проверки, причем делать это совсем в другом стиле. И обычно этого никто не помнит и не делает, многие даже не знают о таком.

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

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

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

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

В вполне логичном предположении, что shared_ptr не кидает исключений

Это с чего бы такая логика? Если деструкторы могут бросать исключения, то почему shared_ptr не должен? Или почему std::string не должен? Или почему std::map::iterator не должен? Или почему объект дерева внутри std::map не должен?

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

Повторю еще раз свой совет: попробуйте сделать «правильный» С++ и походите по грабелькам, которые на этом пути разбросаны. Мозги на место быстро встанут.

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

Это с чего бы такая логика?

Потому, что ему не зачем это делать.

Если деструкторы могут бросать исключения, то почему shared_ptr не должен? Или почему std::string не должен? Или почему std::map::iterator не должен? Или почему объект дерева внутри std::map не должен?

Какие-то должны, какие-то нет. Это все должно быть в документации. Как и в случае любых других функций и методов.

Такая интересная избирательность.

У тебя все методы и функции могут кидать исключения? А почему деструкторы все должны?

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

Шаблоны? Не, не слышал.

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

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

Шаблоны? Не, не слышал.

Слышал. С этим вполне все согласуется. Даже в случае шаблонов есть вещи, которые не кидают исключения. Если вы используете shared_ptr, то ему просто не на что кидать исключение при уничтожении, если сам объект не будет уничтожен.

идиотизм просто превзошел границы

Жаль, что не вышло конструктивной дискуссии.

Шли бы вы строем в свой уютненький мир безопасных языков с со сборщиком мусора.

Я пишу на C++. Вполне умею писать exception-safety код.

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

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

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

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

Очень странно, что вы не видите изъян в языке.
Но тут явно что-то не так...

Ну и в догонку любителям исключений и потоков C++ :-)

#include <fstream>
#include <iostream>

int main(int argc, char *argv[])
{
  std::ifstream f;
  f.exceptions(std::ios_base::failbit);
  try {
    f.clear(std::ios_base::failbit);
  } catch (std::ios_base::failure& e) {
    std::clog << "It will never be catched :-) Hahaha :-)" << std::endl;
  }
  return 0;
}

Исключения то ещё и не всегда ловятся :-) Лол :-)

~ $ g++ --version
g++ (GCC) 5.3.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

~ $ g++ oops.cpp -ooops
~ $ ./oops
terminate called after throwing an instance of 'std::ios_base::failure'
  what():  basic_ios::clear
Aborted
~ $ g++ -std=c++14 oops.cpp -ooops
~ $ ./oops
terminate called after throwing an instance of 'std::ios_base::failure'
  what():  basic_ios::clear
Aborted
anonymous
()
Ответ на: комментарий от anonymous

Это вопрос из той же категории, что и «как быть здоровым и богатым, а не бедным и больным».

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

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

А как из деструктора отменить удаление?

А зачем отменять удаление объекта, которым всё равно воспользоваться невозможно?

Давай всё на том же примере с файлами. Вот есть у нас объект класса файл, внутри у которого хендл лежит. В деструкторе возникла проблема: ну так кто мешает с список складывать именно хендлы? Нужна какая-то ещё информация? Её тоже можно сохранить - всё равно эту логику (в плюсах) самим писать надо.

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

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

Я в этом репозитории вижу кондовый Си-шный код и небольшую C++-оболочку на Qt. У меня тоже есть пара проектов, где работают сервисы на Си, а к ним идёт гуй под Windows на C#. Это не значит, что я вдруг стану писать на C# дальше. Не катит эта ссылка, короче.

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

А был бы тогда C++11 и было бы всё печальней.

Вряд ли. Логику «abort/retry/ignore» прямо в деструктор и поместить - ничего от этого не поменяется.

Но возможность проглотить должна быть! А её отбирают!!!

Нет. Ну или не совсем.

Как было раньше: если деструктор кидал исключение во время размотки стека, то вызывается terminate и всё. Поэтому в 90% случаев в нетривиальных деструкторах всё равно делали try/catch. То есть, если код менять нельзя, то выбора всё равно нет. Проблема может молча игнорироваться и ты, как пользователь библиотеки, о ней не узнаешь. Или terminate.

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

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

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

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

Я тоже, но тут дело не столько в LORе, а в том, что таких людей вообще мало. В любом случае, мне почитать (даже откровенные срачи) бывает интересно.

И когда оказываешься в ситуации вроде вот такой:

Возможно, мне везло и в таких ситуациях не оказывался. В любом случае, мне кажется, что если человек не дурак, то он не будет судить о чём либо по байкам с форумов. Если же такой человек оказался «у власти», то он найдёт чем удивить и от таких лучше держаться подальше. Даже если он будет с пеной у рта защищать С++.

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

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

Ну вот тот же monk, с которым мы здесь спорим. Явно же не дурак. Явно знает не мало. И явно по каким-то вопросам к нему можно прислушиваться. Только вот, например, если некий Вася Пупкин убедился, что к мнению monk-а можно прислушиваться, и вдруг узнает от monk-а — что C++ говно с дырявыми абстракциями, то захочет ли Вася Пупкин нырять глубже?

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

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

Так что free functions (статические методы) для сериализации is the way to go.

Так я и не спорил.

Ну и, вроде, встречаются рекомендации делать «всё что можно» в виде свободных функций, а не методов. И определённая логика тут есть.

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

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

Да. Вот никак не могу найти видеодоклад про это.

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

Саттер, Александреску, «Стандарты программирования на С++. 101 правило и рекомендация». Совет №44: Предпочитайте функции, которые не являются ни членами, ни друзьями.

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

В объяснениях какой-то конторы, почему она перевела свои проекты с C++ на Java.

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

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

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

Тема про RAII. В С++ все хорошо, пока ресурс гарантировано можно «закрыть».

Когда операция закрытия может порождать ошибки, тогда RAII(в его C++ воплощении) не применим и приходится ресурсами управлять руками. Что и несколько дискредитирует RAII, т.к. на практике освобождение ресурсов таки может не сработать.

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

Того, что по старому стандарту программа не должна падать, а по новому должна.

Ты это серьёзно?..

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

Дело не в стандарте, а в том как код написан.

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

Я думаю, что речь о таком вот:

#include <iostream>
using namespace std;
 
struct A
{
	~A() { throw 1; }
};
 
int main()
{
	try
	{
		A a;
	}
	catch (int x)
	{
		std::cout << "catch: " << x << std::endl;
	}
	return 0;
}

Раньше это было ок, а начиная с C++11 программа будет падать.

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

Тема про RAII.

Ну так расшифруйте таки RAII. Ну или почитайте, что это и зачем. Какие есть плюсы, какие есть минусы.

А то вобьют себе в бошку, что RAII — это волшебная пилюля, которая позволит им автоматически транзакции в БД коммитить и подробный лог неприятности при этом вести. И бегают потом по форумам с криками «Ну как же так! Ну нам же обещали! Ну мы же думали!».

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

Ну так расшифруйте таки RAII. Ну или почитайте, что это и зачем. Какие есть плюсы, какие есть минусы.

Да, спасибо. Думаю, что я в теме.

А то вобьют себе в бошку, что RAII — это волшебная пилюля, которая позволит им автоматически транзакции в БД коммитить и подробный лог неприятности при этом вести.

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

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

Ну меня бы устроило, если бы это были просто «вложенные» исключения относительно текущего(первого).

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

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

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

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

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

Т.е. ты его никогда не проверял?

suppressed exceptions? вероятно, нет, хотя бы потому, что мало Java 1.7 кода написал. но в целом знаю как оно работает, и если тип исключения будет важен, то буду проверять.

Если так рассуждать, то и в С++ можно безопасно кидать исключения из деструктора:

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

то try-with-resources даже не скажет тебе, что дверь отвалилась.

там много нюансов. это просто синтаксический сахар для типичного варианта использования.

И обычно этого никто не помнит и не делает

да, от ошибок он не избавляет.

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

Думаю, что я в теме.

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

Вот вы говорите о файлах. Мол, у меня объект file_wrapper, который в деструкторе дергает fclose и этот fclose может завершиться неудачно, а я об этом не узнаю. А посему в языке косяк, а RAII себя дискредитровал.

Я же смотрю на это по другому. У вас есть объект некого типа T, который вам больше не нужен и который разрушается. Он вам уже не нужен, вы его удаляете. Так с какого хрена вы хотите знать о чем-то, что случается в момент смерти уже не нужного вам объекта?

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

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

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

Название говорит само за себя.

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

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

Попробуйте, ради интереса, прикинуть, как будет выглядеть удаление нескольких объектов из парочки согласованных контейнеров (а-ля Boost.MultiIndex)

А в чём проблема? Во втором контейнере, наверное, хранится указатель на объект? Просто убрать его сначала или перехватить исключение, убрать, а потом перебросить. Или я что-то упускаю?

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

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

Я всё-таки не понимаю в чём проблема. Пытаемся удалить объект из первой коллекции внутри try блока. При вылетевшем исключении всё равно пробуем удалить из второй. Ну и объединяем возникшие исключения.

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

А в чём проблема? Во втором контейнере, наверное, хранится указатель на объект? Просто убрать его сначала или перехватить исключение, убрать, а потом перебросить.

В том, что логика сильно усложняется. Плюс появляется try/catch почти на каждый чих.

Или я что-то упускаю?

А вот этот вопрос при подобных раскладах всегда бы оставался неотвеченным :)

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

Вот вы говорите о файлах. Мол, у меня объект file_wrapper, который в деструкторе дергает fclose и этот fclose может завершиться неудачно, а я об этом не узнаю. А посему в языке косяк, а RAII себя дискредитровал.

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

Я же смотрю на это по другому. У вас есть объект некого типа T, который вам больше не нужен и который разрушается. Он вам уже не нужен, вы его удаляете. Так с какого хрена вы хотите знать о чем-то, что случается в момент смерти уже не нужного вам объекта?

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

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

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

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

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

Да, потому что этот способ управления ресурсами

Вот скажите, пожалуйста, что мешает вам вместо слова «управления» использовать слово «очистка»? Ведь тогда вы будете гораздо ближе к пониманию роли RAII.

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

Вот скажите, пожалуйста, что мешает вам вместо слова «управления» использовать слово «очистка»? Ведь тогда вы будете гораздо ближе к пониманию роли RAII.

Пусть будет очистка. Корректно очистить RAII не позволяет в общем случае. Об этом тут и идет дискуссия. Операция закрытия точно так же может не срабатывать. И эти ошибки ничем не отличаются от других. Но RAII в C++ предлагает нам их давить, игнорировать и пр.

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

Операция закрытия точно так же может не срабатывать. И эти ошибки ничем не отличаются от других. Но RAII в C++ предлагает нам их давить, игнорировать и пр.

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

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

Как по мне, так все логично.

Ну и, как я посмотрю, обсуждать здесь нужно не то, что «Корректно очистить RAII не позволяет в общем случае», а то, что так много дятлов вбили себе в башку, что RAII должно позволять это делать. Уж простите мне мой французский.

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

Один из этих дятлов - Страуструп, который, например, в D&E описывает, что finally ненужен и что RAII дает универсальный способ закрытия ресурсов при любом варианте исполнения.

anonymous
()

Кто вообще сказал, что RAII это про освобождение без ошибок?

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

Когда операция закрытия может порождать ошибки, тогда RAII(в его C++ воплощении) не применим

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

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

Кто-то без одного лёгкого живёт.

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

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

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

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

finally вы просто пишете код. Там можно проверить результат функции закрытия/отловить исключение, прокинуть исключение далее и пр. Так же в той же Java «подавленные» исключения можно посмотреть. Тоже самое там происходит и в try-with-resources.

В питоне в методе __exit__ вам доступно текущее исключение(в C++11 тоже, да) и даже остановить раскрутку. Если вы кините исключение из __exit__ оно просто станет текущим. Ничего не упадет, как в C++.

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

Я в этом репозитории вижу кондовый Си-шный код и небольшую C++-оболочку на Qt

Кода на С++ там таки ощутимо больше. А еще это софтину в основном пишет как раз не Торвальдс. Ну и стоит отметить, что раньше она была на GTK, но авторы были от него настолько в шоке, что будучи фанатичными сишниками решили осилить ненавистный им С++.

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

Питон
Java

Хорошо, когда твой язык исполняется в маняокружении под названием «виртуальная машина».

К сожалению (точнее, к счастью), C++ работает в реальном окружении.

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