LINUX.ORG.RU

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

 


2

5

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

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


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

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

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

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

Да и из конструктора тоже. Ну и вообще лучше не бросать исключения наружу из классов.

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

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

ОП ничего про catch не говорил.

ну и даже если ловить, если после точки с бороском шло освобождение ресурсов, то они потекут:

~B() {
    delete this->resource;  // throws
    close(this->socket);    // leaks
}
x0r ★★★★★
()
Ответ на: комментарий от const86

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

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

бросок исключения из конструктора дает объект в неопределенном состоянии

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

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

ну и даже если ловить, если после точки с бороском шло освобождение ресурсов, то они потекут

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

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

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

А как можно быть в этом уверенным?

Gvidon ★★★★
()

Какой delete вызвать для расположенной на стеке переменной?

anonymous
()

Какой профит от того, что если в деструкторе было брошено исключение

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

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

Что за средневековые суеверия?

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

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

Для этого придумали abort()

Там и послание изложено максимально подробно.

anonymous
()

Из деструктора нельзя кидать исключения потому что, если он был вызван для раскрутки стека из-за другого исключения, то будет вызван std:terminate, то есть усё гакнется. Можно городить огород из std::uncaught_exception(), но лучше запомнить простое правило «из деструкторов не кидают исключений и ловят все гипотетические».

user@xxx:/tmp$ cat ex.cc
#include <exception>
#include <iostream>

class Test{
public:
   ~Test()
   {
          throw 10;
   }
};

void g()
{
   try{
    Test d;
        throw 20;
        }  catch(int i)
      {
         std::cout << i << std::endl;
      }
         std::cout << "alive" <<  std::endl;
}

int main()
{
  g();
}
user@xxx:/tmp$ g++ ex.cc
user@xxx:/tmp$ ./a.out
terminate called after throwing an instance of 'int'
Аварийный останов
vtVitus ★★★★★
()
Ответ на: комментарий от const86

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

А как быть со статическими переменными с таким деструктором?
А с массивами таких переменных, чего делать?

А ещё STL лучше не использовать.

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

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

лучше запомнить простое правило «из деструкторов не кидают исключений и ловят все гипотетические»

лучьше знать что и почему произойдет далее. Ведь это наша профессия.

Вопрос все еще актуален. Пример для затравки

#include <iostream>

class A
{
public:
	A() {std::cout << "A()\n";}
	~A() {std::cout << "~A()\n";}
};

class B
{
public:
	B() {std::cout << "B()\n";}
	~B() {std::cout << "~B()\n";}
};

class Base
{
public:
	Base() {std::cout << "Base()\n";}
	~Base() {std::cout << "~Base()\n";}
};

class Test: public Base
{
public:
    Test(){
        std::cout << "Test()" << std::endl;
    }
	~Test(){
		std::cout << "~Test()\n";
		throw 1;
	}

    void* operator new (std::size_t size) throw (std::bad_alloc) {
        std::cout << "Test::operator new(" << size << ")" << std::endl;
        return ::operator new(size);
    }

    void operator delete (void* ptr) throw (std::bad_alloc) {
        std::cout << "Test::operator delete()" << std::endl;
        ::operator delete(ptr);
    }
	A a;
	B b;
};

int main() {
	Test *t;
	try
	{
    	t = new Test();
    	delete t;
	}
	catch(...)
	{
		std::cout << "shit\n";
	}
}
Если не кидаем исключения
Test::operator new(2)
Base()
A()
B()
Test()
~Test()
~B()
~A()
~Base()
Test::operator delete()
Если кидаем исключение (наважно в каком из деструкторов, исключение кидается после вывода записи на консоль)
Test::operator new(2)
Base()
A()
B()
Test()
~Test()
~B()
~A()
~Base()
shit
И еще интерестный момент, если мы кидаем исключение из конструктора, то для всех частей для которых мы уже успели вызвать конструктор - будет вызван деструктор. А также, ВНИМАНИЕ, память то удалиться тоже! (исключение было брошено из конструктора класса B)
Test::operator new(2)
Base()
A()
~A()
~Base()
Test::operator delete()
shit

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

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

Это я и имел в виду. Два различных варианта логики в каждом деструкторе. Нафиг нужно такое счастье?

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

Оно нужно не всегда и не везде, и вообще uncaught_exception не для таких вещей :)

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

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

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

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

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

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

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

Бросок исключения из конструктора приводит к тому, что объекта нету. О каком неопределённом состоянии идёт речь? Что бы писать правильные конструкторы надо знать про raii и смартпоинтеры.

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

Так ведь если исключение было в конструкторе то так и происходит.

Открой для себя полностью и неполностью сконструированные объекты.

Constructor() {a = new Smth; throw 1; }

ничего никуда не вернёт.

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

откуда столько неосиляторов? хватит уже распространенять этот бред про исключения в конструкторах.

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

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

А как быть со статическими переменными с таким деструктором?

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

const86 ★★★★★
()
Ответ на: Не надо размышлять, я уже ответил от anonymous

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

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

лучше запомнить простое правило «из деструкторов не кидают исключений и ловят все гипотетические»

лучьше знать что и почему произойдет далее. Ведь это наша профессия.

Чертовски верная мысль!

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

Бинго!

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

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

Всё правильно сказал.

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

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

Да ты жжошь...

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

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

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

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

Эммм, а память уже выделенная под переменные объекта? Потечет? :/

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

ох и враки же %)

откройте для себя RAII, и никакой неопределенности.

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

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

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

void monkey() {
    try {
        auto a = std::shared_ptr<A>(new A());
        a->myMethod();
        this->anotherMethod(a);
    } catch {
        // blah
    }
}
вместо
void monkey() {
    auto a = std::shared_ptr<A>(new A());
    try {
        a->myMethod();
    } catch {
        // blah
    }
    this->anotherMethod(a);
}
оно то можно, но не понятно зачем оборачивать в try чтото, что ничего не бросает.

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

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

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

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

ты не знаешь, что в конструкторе проинициализировалось, а что нет.

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

или о чем речь?

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

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

во время чтения файла я ловлю eof и получаю частично инициализированный объект. состояние объекта не определено. RAII тут както не поможет, eof выбрасывать наверх не комильфо, вызывающий контектс вообще не должен знать, что мы полезли в файл в конструкторе. можно выбросить свой ексепшн наверх, но опять таки меня это не улыбает:

A* a;
try {
    a = new A();
} catch {
}

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

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

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

user@xxx:/tmp$ cat ex2.cc
#include <exception>
#include <iostream>

class Base {
        public:
        virtual ~Base() {
                std::cout << "Base" << std::endl;
        }

};

void somecall(int i) {
        if (i == 0) throw i;
}

class Test : public Base {
        int i;
public:
        Test(int _i):i(_i){}

        virtual ~Test()
        {
                std::cout << "Test" << std::endl;
                somecall(i);
                close();
        }

        void set(int _i) {
                i = _i;
        }
        void close() {
                std::cout << "close " << i << std::endl;
        }
};

void g()
{
        Test * d1 = new Test(0);
        try {
                delete d1;
        }  catch(int i) {
                std::cout << "exception " << i << std::endl;
                d1->close();
                try {
                        delete d1;
                } catch(int i) {
                        std::cout << "no exception " << i << std::endl;
                }
        }
}

int main()
{
  g();
}
user@xxx:/tmp$ g++ ex2.cc
user@xxx:/tmp$ ./a.out
Test
Base
exception 0
close 0
Base

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

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

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

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

Думаю, так:

  • Для любой области генерируется код раскрутки стека, который вызывает деструкторы всех объектов, которые были созданы в этой области до возникновения исключения.

    В области видимости ~Test() такие объекты - это A, B, Base. Компилятор генерирует одинаковый код для любых областей, в том числе для деструктора.

  • terminate() вызывается в двух случаях: а) если исключение вышло за main() и б) если исключение генерируется во время раскрутки стека (в gcc это видимо проверяет __cxa_throw).

    Больше никаких проверок компилятор не вставляет (для простоты реализации). Когда исключение генерируется в ~Test(), ни одно из двух условий не выполнено.

  • Когда в деструкторе было сгенерировано исключение, выполнялась реализация delete, которая выглядит примерно так:
    test->~Test();
    test->operator delete();
    

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

    Можно было бы требовать от компилятора такой реализации:

    try {
      test->~Test();
    } catch (...) {
      test->operator delete();
      throw;
    }
    test->operator delete();
    

    Однако в обоих случаях результат - UB. Если в результате все равно UB, нет смысла требовать более сложной реализации от компилятора.

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

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

А это фишка gcc, баг gcc или прописано в стандарте?

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

Хороший пример получился. Правда не понятно стандартное это поведение или особенность g++

user@xxx:/tmp$ cat ex2.cc
#include <exception>
#include <iostream>

class Base {
        public:
        virtual ~Base() {
                std::cout << "Base" << std::endl;
        }

        void operator delete(void *p) {
                std::cout << "delete Base " << p << std::endl;
        }

};

void somecall(int i) {
        if (i == 0) throw i;
}

class Test : public Base {
        int i;
public:
        Test(int _i):i(_i){}

        virtual ~Test()
        {
                std::cout << "Test" << std::endl;
                somecall(i);
                close();
        }

        void operator delete(void *p) {
                std::cout << "delete Test " << p << std::endl;
        }

        void close() {
                std::cout << "close " << i << std::endl;
        }
};

void g()
{
        Test * d1 = new Test(0);
        try {
                delete d1;
        }  catch(int i) {
                std::cout << "exception " << i << std::endl;
                d1->close();
                try {
                        delete d1;
                } catch(int i) {
                        std::cout << "no exception " << i << std::endl;
                }
        }
}

int main()
{
  g();
}

user@xxx:/tmp$ g++ ex2.cc
user@xxx:/tmp$ ./a.out
Test
Base
exception 0
close 0
Base
delete Base 0x99e010

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

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

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

Эммм, а память уже выделенная под переменные объекта? Потечет? :/

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

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