LINUX.ORG.RU

Исключение в деструкторе

 


2

5

Какой профит от того, что если в деструкторе было брошено исключение (и оно пока единственное активное), то все равно продолжат вызываться деструкторы суб-классов и базовых классов, но при этом не будет выполнен ::operator delete(void*), т.е. физически память не будет освобождена (по стандарту, да и на практике).

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


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

Чтоб можно было доделать то, что было не доделано в деструкторе
Повторный вызов delete не будет вызывать деструктор (базовый вызовет!!!)

Задумайтесь, у вас повторно вызывается деструктор базового класса и всех объктов субклассов. Я бы сказал это нежелательный побочный эффект, а не изначальная задумка. К тому же если вы не сделаете виртуальный деструктор в базовом классе (простой обычный деструктор) то у вас и деструкторо основого класса тоже быдет вызван.

В общем хрень эпичная выходи.

Cupper
() автор топика
Ответ на: комментарий от x0r

Откуда вы такие беретесь? Неужели ты пишешь на С++ за деньги? Мне иногда жаль, что есть дефицит «программистов».

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

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

Cupper
() автор топика
Ответ на: комментарий от x0r

Надо ловить, чистить и кидать дальше.

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

Ваще чума :)

Теперь на вопрос «зачем нужен виртуальный деструктор?» буду отвечать

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

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

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

повторно вызывается деструктор базового класса и всех объктов субклассов

Это я лиху дал. В случае если деструкторы не виртуальные, то так и есть. Если же виртуальные, вся часть наследника игнорируется, а для всего что вы ше повторно вызываются деструкторы.

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

Хотя если придираться к стандарту, то он не утверждает что исключение в деструкторе приводит к UB но, утверждает что память не будет освобождена.

Да, действительно.

Вот еще интересный пост: http://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/

Там автор пишет (не проверял):

Even more interestingly, although it is C++11-specific (C++03 is not clear about this behavior), if you allocate an object (including an array) using operator new and later call operator delete to call the destructor and free memory, if destructor of your object throws, the deallocation function is still called preventing memory leak.

И еще про C++11:

In C++11, a destructor is implicitly specified as noexcept.

А также пример того, почему неправильно использовать std::uncaught_exception(), про который говорили выше в треде.

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

Не, ну понятно что со статическими объектами(а умный указатель таким и является по сути) всё будет ок. Вопрос то как бы не об этом :-)

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

А в чем вопрос? Если кто-то слишком туп или просто не владеет инструментом, то виноват не инструмент.

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

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

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

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

Мне еще не пригодились исключения ни разу ни в конструкторе

Эммм. А это как? Память не выделяешь, файлы не открываешь? Что ты в конструкторах вообще делаешь?

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

Выделяю, открываю. Делай всё это через Qt-шные классы, ловлю ошибки через слоты.

Больших проектов на чистом C++ у меня нет

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

именно. в конструкторе инициализируется все стековое и не бросающее. все остальное выносится в load(), open() или initialize() метод. и него тоже ничего не бросается, а есть bool return value.

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

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

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

А о чём? Неужели ты хочешь сказать что код вида

Some::Some()
  :some_mem(0)
{
  throw int(-1);
}
...
while (true) {
 try {new Some;} catch (int) {}
}
со временем сожрёт всю память?

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

Как раз наоборот. Правильный конструктор как раз таки выбрасывает.

Допустим есть класс файл. Я хочу файл открыть и прочитать от туда байтики. Интерфейс объекта это позволяет. Я создаю объект класса файл, передавая конструктору имя файла. Но беда - файла с таким именем на диске нет. Твои действия? Можешь описать, как бы ты реализовал этот класс, и как бы он себя вёл в описаном выше случае?

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

Эпик фейл. Иди читай книжки по спп.

Эпик фейл явно не у меня. То, что этот умный указатель содержит в себе определенные данные и имеет конструктор и деструктор я знаю. Но это не делает его «снаружи» динамическим объектом.

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

У тебя что-то с терминологией не то.

Статический объект - это когда он в памяти уже тогда, когда ещё программу не запустили.
Автоматический объект - это когда он размещён в автоматической памяти (размещать смарт поинтеры где-то ещё - это ли не венец идиотизма?)

Но это не делает его «снаружи» динамическим объектом.

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

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

Не надо бросать исключения из деструктора.

Что за средневековые суеверия? Бросай сколько хочешь, коли уверен, что мимо не летит другое, ещё не пойманное исключение

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

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

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

И вообще лучше не использовать исключения. А ещё STL лучше не использовать. И шаблоны

С этим уже не ко мне

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

Делать смарт поинтер «динамическим объектом» (если я правильно тебя понял) - венец идиотизма.

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

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

Я прекрасно понимаю как работают указатели(как обычные, так и «умные»), так что не надо.

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

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

Делать обычный указатель на смарт-пойнтер - мягко говоря потерять все его преимущества.

Ну я то же самое имел ввиду.

А в том, что случится с выделенной памятью в конструкторе при появлении исключения.

С какой имено памятью, уточни? Та, что для полей класса? Или ты инициализируешь какой-то указатель (член класса) оператором new?

В первом случае - ничего, её удалят.

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

Короче, я надеюсь ты в курсе, что такое exception safety в констукторе или нет? Если да - не парь мне мозг, если нет - иди читай книжки по спп.

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

Если сделать по нормальному - то да, т.к. все автоматические объекти (и тот же проинициированый твоей памятью unique_ptr) будут удалены.

Ну так сам-то unique_ptr для конструктора будет статическим объектом, с этим никаких вопросов нет.

Если криворукий - то нет, получишь течь.

Хехехе. Ну значит я теперь буду троллить знакомых программеров на эту тему :-)

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

будет статическим объектом

Каким нхр статическим? Ты уже реально задолбал. Если он будет статическим то никой дурак рантайм его удалять не будет. Надо что бы он был автоматическим. Это сложно понять?

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

Каким нхр статическим? Ты уже реально задолбал. Если он будет статическим то никой дурак рантайм его удалять не будет. Надо что бы он был автоматическим. Это сложно понять?

Для меня объекты либо статические либо динамические. Статические управляют памятью сами, для динамических память разгребает программист(new/delete и т.д.). Автоматический объект в данном случае - не более чем buzzword в новом стандарте

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

Для меня

ничего не слышу ничего не вижу?

struct S
{
 static int i;
};

struct M
{
  int i;
}

....

M m1;
M m2;
S s1;
S s2;

Как ты считаешь, какая из них (S::i или M::i) статическая, а какая динамическая? И сколько всего памяти было выделено (и когда) под все S::i и под все M::i?

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

Мы начали разговор про статические объекты, а не про статические переменные. Что такое статическая переменная я знаю. В случае классов память будет выделена один раз и переменная будет одна на весь класс(а не по одной на объект). Я вел речь не об этом.

В некоторых источниках я встречал такую терминологию

int* i - pointer on dynamic object of type int
int i - static object of type int

Такие дела. Как у них называется static int i - не помню.

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

про статические объекты

ути-пути какой. А разница та какая?

struct I
{
   int i;
}

struct S
{
   static struct I i;
};

int i - static object of type int
Такие дела.

Уверен, такими источниками надо одно место подтирать, если там действительно так пишут. Можешь ссылку дать на источник?

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

Ну офигеть теперь. Бросай сколько хочешь, ага.

Необходимость думать никто не отменял. Согласен, вряд ли на практике встретится ситуация, где будет резон кидать исключения из деструктора. Претензия была к неуместному «правилу хорошего тона». Такие правила годятся для команды недалёких умом кодеров, но не годятся как ответ на вопрос ТСа. Вопрос сам по себе подразумевает желание разобраться, а не выслушивать «бу-бу-бу, маму слушай, в носу не ковыряй» ;)

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

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

Уверен, такими источниками надо одно место подтирать, если там действительно так пишут.

В оригинале Meyers S. «More Effective C++», например.

У Страуструпа в словаре(http://www.stroustrup.com/glossary.htm) определение более строгое, так что да, скорее всего ты прав. Ибо к кому еще апеллировать :-)

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

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

class File {
public:
    File(const std::string& name):
            fileName(name) {
    }

    bool open() throw();
    bool read(char* buffer) throw();
    void close() throw();

private:
    std::string fileName;
};

void func(File& file) {
    if (file.open()) {
        file.read();
        file.close();
    }
}

File file("/dev/none");
func(file);

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

В принципе разумно, но не для спп. Что ты будешь делать с таким подходом, если тебе надо оптимизировать func? Открывать/закрывать файл в каждом вызове затратно.

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

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

И теряешь все преимущества RAII. Такой подход, по сути, ничем не лучше использования простых сишных функций

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

это только пример с open/close в одном контексте, close() вполне вызываем в деструкторе, open() при первом обращении.

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

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

причины всегда инициализировать N ресурсов тупо инстанцируя их классы-холдеры нет.

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

Поверь, я видел классы, в которых были поля bool initialized, или initialized_ok. По задумке, каждая собака, которая обращается к объекту, должна была этот флаг проверять. Но (что не удивительно), никто (никто-никто, даже автор этого чуда) этого не делал. Этот код был в длл, которое как раз с устройством комуницирует. Сам апликейшн - одного производителя, длл - другого, договаривались только про апи длл, ну и таймауты были тоже оговорены немного. Так вот, при первом же кипише с устройством, вся система (в смысле весь аппликейшн, который помимо комуникации с девайсом делал ещё сотню кучь разных вещей) вылетал с сообщением сегментатион фаульт, без объяснения причин и всяких там стек трейсов. Перезапуск занимал приблизительно минут 15. Вся эта кухня была установлена на psi sls, где сутки работы источника стоят тыщь пятдесят €.

Так вот чувак. Мораль проста: Для наколенных поделок типа веба два.ноль, можешь что угодно использовать, можешь даже ошибки и не проверять совсем, пускай валится себе, и пускай все подождут ©. Но я для себя выработал правило, которое очень хорошо работает и я советую взять тебе его на вооружение (ну если ты на спп пишешь вообще, хотя может быть для йавы оно тоже применимо): когда у тебя объект представляет некую сущность в твоей программе - нет сущности/не удалась инициализация - бросай исключение прямо из конструктора. Нет неправильного объекта == нет проблем.

Это действительно работает! ©
Я гарантирую это ©

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

причины всегда инициализировать N ресурсов тупо инстанцируя их классы-холдеры нет.

Есть. Представь, инициализация - секунды, а измерение - миллисекунды.

class Device
{
 public:
  Device();
  void do_one_measure(int measure_time_in_ms);
};
Сколько времени уйдёт на первое измерение? Будут ли полученые результаты верны? Будут ли они вообще важны?

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

Но я для себя выработал правило

ну вот и пользуйся :)

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

ну и? будет у тебя:

class Device
{
 public:
  Device() = default;
  bool initialize();  // 1000 ms
  void do_one_measure(int measure_time_in_ms);  // 1ms
};

if (dev.initialize()) { dev.do_one_measure(&timestamp); }

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

Больше никаких проверок компилятор не вставляет (для простоты реализации).

Есть проверка на соответствіе throw() тому что кинулось.

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

кажется было у страуструпа в его третьем The C++ Programming Language.

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

когда у тебя объект представляет некую сущность в твоей программе - нет сущности/не удалась инициализация - бросай исключение прямо из конструктора. Нет неправильного объекта == нет проблем.

уточню: не знаешь что делать - бросай эксэпшан.

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

Есть проверка на соответствіе throw() тому что кинулось.

Да, точно.

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

bool initialize()
if (dev.initilize())

Ты внимательно читал то, что я тебе писал? Это говно как раз на практике и не работает, потому что всегда найдётся говнокодер, который будет делать так:

Device *d = Device();
d->do_one_measure(meas_time);

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

Да и вообще. Пойми наконец, что в этом случае у Device будет два состояния - проинициализирован и нет. Если смотреть на твою прогу как на fsm, то с таким подходом он будет ровно в два раза сложнее.

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

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

когда у тебя объект представляет некую сущность в твоей программе - нет сущности/не удалась инициализация - бросай исключение прямо из конструктора. Нет неправильного объекта == нет проблем.

Не догнал. Ты в конструкторе открываешь объект - распределил память под какие-то его свойства malloc'ом, или оператором new. Например у тебя объект «компьютор» и ты создаёшь объекты для его подсистем - DMA, видяхи, RTC etc. Для каждого из них ты создаёшь объект и на каком-то шаге обламываешься, потому что объект сдох или произошло что-то непредвиденное.Ты просто бросаешь исключение или создаёшь стихотворение в стиле Маяковского, которое будет последовательно освобождать всё, что раньше создал/распределил во избежание утечек памяти? У тебя какой экран в ширину чтобы писать более или менее сложные объекты?

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

доедай борщ и открывай учебник «с++ для чайников»

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

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

class Some
{
  Resource1 *r1;
  Resource2 *r2;
  Resource3 *r3;
  
  public:
    Some(int ident);
    ~Some();
};

Some::Some(int ident)
{
  unique_ptr <Resource1> _r1(new Resource1(ident));
  unique_ptr <Resource2> _r2(new Resource2(ident));
  unique_ptr <Resource3> _r3(new Resource3(_r2.get())); //<- this way works as well
  
  r1 = _r1.release();
  r2 = _r2.release();
  r3 = _r3.release();
}

Some::~Some()
{
  delete r1;
  delete r2;
  delete r3;
}

Но можно ещё лучше (я так и делаю обычно)

class Some2
{
  unique_ptr <Resource1> r1;
  unique_ptr <Resource2> r2;
  unique_ptr <Resource3, function <void (*)(Rousrce3 *)> > r3;  //custom deinit
  
  public:
    Some2(int ident);
}

Some2::Some2(int ident)
    :r1(new Resource1(ident)),
    r2(new Resource2(ident)),
    r3(new Resource3(r2.get()), [] (Resource3 *r) {r->any_deinit_must_be_done_before_delete();})
{
  if (!r1->do_some_tricky_and_return_true()) {
    throw Some2Exception("r1 tricky failed");
  }
}

nanoolinux ★★★★
()
Последнее исправление: nanoolinux (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.