Исключение в констректоре и дестректоре
От: DioNNiS http://i-liger.com
Дата: 14.07.10 18:23
Оценка:
Здравствуйте!

Подскажите, что произойдет, если:
1. в конструкторе будет вызвано исключение
2. в дестреторе будет вызвано исключение

Эти вопросы интересны и для С++

Заранее сапасибо!
Владея информацией, владеешь миром. Уинстон Черчилль
Re: Исключение в констректоре и дестректоре
От: _FRED_ Черногория
Дата: 14.07.10 19:16
Оценка:
Здравствуйте, DioNNiS, Вы писали:

DNN>Подскажите, что произойдет, если:

DNN>1. в конструкторе будет вызвано исключение

Ничего особенного по сравнению с исключением в другом методе.

DNN>2. в дестреторе будет вызвано исключение


Язак коверкается намеренно? Очень сбивает чтение и вызывает весьма неприятные эмоции к автору. За такое по праилам форума можно и в баню угодить. В такую-то жару? А бассейна там никто не обещал.



DNN>Эти вопросы интересны и для С++


Если не ошибаюсь — "деструктор" там — это обычный Dispose() в шарпе, а файнализатор — да он и в африке файнализатор.
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Исключение в констректоре и дестректоре
От: DioNNiS http://i-liger.com
Дата: 14.07.10 20:07
Оценка:
Здравствуйте, _FRED_, Вы писали:

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


DNN>>Подскажите, что произойдет, если:

DNN>>1. в конструкторе будет вызвано исключение

_FR>Ничего особенного по сравнению с исключением в другом методе.


Вопрос заключается в том, будет ли вызван деструктор у объекта? И вообще будет ли замечен в данном случае объект сборщиком мусора.

DNN>>2. в дестреторе будет вызвано исключение


_FR>Язак коверкается намеренно? Очень сбивает чтение и вызывает весьма неприятные эмоции к автору. За такое по праилам форума можно и в баню угодить. В такую-то жару? А бассейна там никто не обещал.


Прошу прощение, просто опечатался, не специально

_FR>

DNN>>Эти вопросы интересны и для С++


_FR>Если не ошибаюсь — "деструктор" там — это обычный Dispose() в шарпе, а файнализатор — да он и в африке файнализатор.


Я хочу уточнить, что интересно, будет ли у классов с исключением в констректоре вызван деструктор?
И будет ли считаться потёртым, класс с исключением в деструкторе?
Владея информацией, владеешь миром. Уинстон Черчилль
Re[2]: Исключение в констректоре и дестректоре
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 14.07.10 21:10
Оценка: 39 (5)
Здравствуйте, _FRED_, Вы писали:


DNN>>Эти вопросы интересны и для С++


_FR>Если не ошибаюсь — "деструктор" там — это обычный Dispose() в шарпе, а файнализатор — да он и в африке файнализатор.


В С++ с исключениями в "деструкторе" все просто. НИКОГДА НЕ БРОСАТЬ ИСКЛЮЧЕНИЙ ИЗ ДЕСТРУКТОРОВ. Поскольку в противном случае у вас будут утечки памяти при попытке поместить подобный объект в любой контейнер (включая динамический массив). Так что, может и существует более простой способ отстрелить себе ногу, но этот способ весьма надежен на все 100%.

C финализатором тоже все просто:

If Finalize or an override of Finalize throws an exception, the runtime ignores the exception, terminates that Finalize method, and continues the finalization process.


С исключениями же из метода Dispose все не так просто.

Основная проблема генерации исключения из метода Dispose сводится к потенциальному сокрытию исходного исключения при использовании этого Disposable объекта внутри блока using.

Давайте рассмотрим следующий код:

class Foo : IDisposable {
  public void Dispose() {
     throw new MyException1();
  }
}

//где-то в коде
using(var f = new Foo())
{
}

//Блок using преобразуется компилятором в следующий блок кода:

var f = new Foo();
try
{
  //используем f
  //и по какой-то причине здесь возникает исключение
  throw new MyException2();
}
finally
{
  if ( f != null ) f.Dispose();
  //Здесь же, в методе Dispose, исходное искючение (MyException1) будет
  //"проглочено" и сгенерировано другое исключение (MyException2)
}


Проблема с исключениями в методе Dispose как раз и лежит в выборе между двумя неидеальными решениями: между сокрытием проблемы в методе Dispose и сокрытием исходной проблемы, которая возникла перед вызовом метода Dispose. Какое решение выбрать — зависит от вас. Нужно сказать, что даже в BCL не всегда придерживаются одной и той же позиции по этому поводу. Так, например, в WCF при попытке вызвать метод Dispose на faulted channel-е, этот метод генерирует свое исключение и скрывает исходное исключение, которое уже может быть расположено в стеке вызовов (см. WCF dispose mask). Существует вариант, когда метод Dispose содержит различную логику в зависимости от того, вызван он благодаря распространению исключения или нет (см. Take a different path when an exception has occurred.), но очень сомневаюсь в том, что разумно изменять поток выполнения (и возможные типы сгенерированных исключений) в зависимости от подобной логики. Так что, какой вариант выбрать — зависит от вас, но в большинстве случаев более предпочтительным будет вариант, при котором метод Dispose не будет генерировать исключения.
Re[3]: Исключение в констректоре и дестректоре
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 14.07.10 21:30
Оценка: 2 (1)
Здравствуйте, DioNNiS, Вы писали:

DNN>Вопрос заключается в том, будет ли вызван деструктор у объекта? И вообще будет ли замечен в данном случае объект сборщиком мусора.


DNN>>>2. в дестреторе будет вызвано исключение


DNN>Я хочу уточнить, что интересно, будет ли у классов с исключением в констректоре вызван деструктор?

DNN>И будет ли считаться потёртым, класс с исключением в деструкторе?


Небольшое уточнение к предыдущему сообщению.

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

В С++, если в конструкторе объекта будет сгенерировано исключение, то деструктор именно этого объекта вызван не будет, но при этом будут вызваны деструкторы всех уже сконструированных подобъктов этого объекта. Проще показать на примере:

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

class B {
public:
  B() {std::cout<<"B::B()"<<std::endl; throw std::exception();}
  ~B() {std::cout<<"B::~B()"<<std::endl;
private:
  A a; //конструктор по умолчанию объекта A будет вызван автоматически
};


Тогда, если мы где-то попробуем создать объект класса B, то получим следующий вывод:

A::A()
B::B()
A::~A()


В языке C# поведение в подобной ситуации будет другим. В .net будет вызван финализатор для не полностью сконструированного объекта, так что, если в конструкторе объекта класса A произойдет исключение, то финализатор класса A в любом случае будет вызван. Но, естественно, в таком случае не может быть вызван метод Dispose (ведь метод Dispose — это все же метод интерфейса, хоть о его существовании и знает компилятор C#), но этот метод вызывается в пользовательском коде, так что без ссылки на объект вызвать этот метод не получится.

Это приводит к тому, что если объект класса A содержит неуправляемые ресурсы (очисткой которых занимается метод Dispose и финализатор), то вам нужно позаботиться о том, что финализатор может быть вызван для частично сконструированного объекта, ну а если у вас есть неуправляемые ресурсы (очисткой которых занимается исключительно метод Dispose), то вам нужно гарантировать, что в случае генерации исключения из конструктора такого объекта, все управляемые подобъекты этого объекта будут очищены.

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("A::A");
        //Захватили ресурсы
        throw new Exception();
    }
    public void Dispose()
    {
        Console.WriteLine("A::Dispose");
    }
    ~A()
    {
        Console.WriteLine("A::~A");
        //Проверили, что ресурсы действительно захвачены 
        //после чего освободили их
    }
}


Результат создания такого объекта будет следующим:

A::A
A::~A


Но строки A::Dispose мы не увидим.
Re[3]: Исключение в констректоре и дестректоре
От: _FRED_ Черногория
Дата: 15.07.10 05:48
Оценка: +1
Здравствуйте, SergeyT., Вы писали:

DNN>>>Эти вопросы интересны и для С++


_FR>>Если не ошибаюсь — "деструктор" там — это обычный Dispose() в шарпе, а файнализатор — да он и в африке файнализатор.


ST>В С++ с исключениями в "деструкторе" все просто. НИКОГДА НЕ БРОСАТЬ ИСКЛЮЧЕНИЙ ИЗ ДЕСТРУКТОРОВ. Поскольку в противном случае у вас будут утечки памяти при попытке поместить подобный объект в любой контейнер (включая динамический массив). Так что, может и существует более простой способ отстрелить себе ногу, но этот способ весьма надежен на все 100%.


Если ты про С++, то там вроде бы вплоть то одного из UB. Я ж говорил про С++/CLI

ST>C финализатором тоже все просто:

ST>

ST>If Finalize or an override of Finalize throws an exception, the runtime ignores the exception, terminates that Finalize method, and continues the finalization process.


То, что я и сказал: как в Африке, то есть как и в шарпе.

ST>С исключениями же из метода Dispose все не так просто.

ST>Основная проблема генерации исключения из метода Dispose сводится к потенциальному сокрытию исходного исключения при использовании этого Disposable объекта внутри блока using.
ST>Давайте рассмотрим следующий код:

Это вроде как с С++ никакого отношения? Кстати, на счёт шарпа недавно где-то ломали копья на эту тему.

ST>Проблема с исключениями в методе Dispose как раз и лежит в выборе между двумя неидеальными решениями: между сокрытием проблемы в методе Dispose и сокрытием исходной проблемы, которая возникла перед вызовом метода Dispose.


Это лишь часть айсберга, техника. Главные, ИМХО, неприятности в том, что исключения в Dispose() чертовски неудобно перехватывать (1) и [очень часто приходится иметь дело не столько с конкретным Dispose, сколько с IDisposable::Dispose()] контракт IDisposable не говорит вызывающему о том, что же там может произойти, и как-то адекватно среагировать на такое исключение (обработать его) вызывающему код невозможно.
Help will always be given at Hogwarts to those who ask for it.
Re[4]: Исключение в констректоре и дестректоре
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 15.07.10 12:27
Оценка:
Здравствуйте, _FRED_, Вы писали:

ST>>В С++ с исключениями в "деструкторе" все просто. НИКОГДА НЕ БРОСАТЬ ИСКЛЮЧЕНИЙ ИЗ ДЕСТРУКТОРОВ. Поскольку в противном случае у вас будут утечки памяти при попытке поместить подобный объект в любой контейнер (включая динамический массив). Так что, может и существует более простой способ отстрелить себе ногу, но этот способ весьма надежен на все 100%.


_FR>Если ты про С++, то там вроде бы вплоть то одного из UB. Я ж говорил про С++/CLI


А чего-то начал проводить аналогии с нормальным С++ , а там, действительно UB, который проявляется, в частности в том, что вызов delete[] работать нормально не в состоянии. Интересно, что имел ввиду топикстартер

_FR>То, что я и сказал: как в Африке, то есть как и в шарпе.



ST>>Давайте рассмотрим следующий код:


_FR>Это вроде как с С++ никакого отношения? Кстати, на счёт шарпа недавно где-то ломали копья на эту тему.

Да, теперь я переключился на .net... Видимо, несколько сумбурно получилось

_FR>Это лишь часть айсберга, техника. Главные, ИМХО, неприятности в том, что исключения в Dispose() чертовски неудобно перехватывать (1) и [очень часто приходится иметь дело не столько с конкретным Dispose, сколько с IDisposable::Dispose()] контракт IDisposable не говорит вызывающему о том, что же там может произойти, и как-то адекватно среагировать на такое исключение (обработать его) вызывающему код невозможно.


Согласен, проблема, действительно не одна. Какая из них серьезней, хз, но их, в целом, достаточно, чтобы крепко задуматься о том, стоит связываться с генерацией исключений из метода Dispose или нет.
Re[5]: Исключение в констректоре и дестректоре
От: _FRED_ Черногория
Дата: 15.07.10 13:12
Оценка: +1
Здравствуйте, SergeyT., Вы писали:

_FR>>Это лишь часть айсберга, техника. Главные, ИМХО, неприятности в том, что исключения в Dispose() чертовски неудобно перехватывать (1) и [очень часто приходится иметь дело не столько с конкретным Dispose, сколько с IDisposable::Dispose()] контракт IDisposable не говорит вызывающему о том, что же там может произойти, и как-то адекватно среагировать на такое исключение (обработать его) вызывающему код невозможно.


ST>Согласен, проблема, действительно не одна. Какая из них серьезней, хз, но их, в целом, достаточно, чтобы крепко задуматься о том, стоит связываться с генерацией исключений из метода Dispose или нет.


Добавлю, что FDG вполне однозначен:

Avoid explicitly throwing exceptions from finally blocks. Implicitly thrown exceptions resulting from calling methods that throw are acceptable.

здесь, в самом низу

Жаль книжки под рукой нет, чтоб проверить, нету ли там каких коментариев к этой записи.
Help will always be given at Hogwarts to those who ask for it.
Re[6]: Исключение в констректоре и дестректоре
От: _FRED_ Черногория
Дата: 18.07.10 19:55
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>>>Это лишь часть айсберга, техника. Главные, ИМХО, неприятности в том, что исключения в Dispose() чертовски неудобно перехватывать (1) и [очень часто приходится иметь дело не столько с конкретным Dispose, сколько с IDisposable::Dispose()] контракт IDisposable не говорит вызывающему о том, что же там может произойти, и как-то адекватно среагировать на такое исключение (обработать его) вызывающему код невозможно.


ST>>Согласен, проблема, действительно не одна. Какая из них серьезней, хз, но их, в целом, достаточно, чтобы крепко задуматься о том, стоит связываться с генерацией исключений из метода Dispose или нет.


_FR>Добавлю, что FDG вполне однозначен:

_FR>

_FR>Avoid explicitly throwing exceptions from finally blocks. Implicitly thrown exceptions resulting from calling methods that throw are acceptable.

_FR>здесь, в самом низу

_FR>Жаль книжки под рукой нет, чтоб проверить, нету ли там каких коментариев к этой записи.


Ага, в обоих изданиях книги текст по данному поводу как оказалось одинаков и отличается от сказанного в MSDN:

AVOID throwing an exception from within Dispose(bool) except under critical situations where the containing process has been corrupted (leaks, inconsistent shared state, etc.)

Users expect that a call to Dispose will not raise an exceptions. For example, consined the manual try-finally in this snippet:

TextReader tr = new StreamReader(File.OpenRead("foo.txt"));
try {
  // do some stuff
}
finally {
  tr.Dispose();
  // more stuff
}

If Dispose could raise an exception, further finally-block cleanup ligic will not execute. To work around this, the user would need to wrap every call to Dispose (within the finally block!) in a try block, wich leads to very complex cleanup handlers. If executing a Dispose(bool disposing) method, never throw an exception is disposing is false. Doing so will terminate the process if executing inside a finalizer process.


То есть, впадать в паранойю и внутри своих Dispose перехватывать всё catch(Exception) не стоит (а то интернеты полны уже классов — помощников\враперов на эту тему — не поддавайтесь). В то же время, нужно отдавать себе отчёт, что исключение в Dispose говорит о том, что случился такой ахтунг, что нужно, как минимум выгрузить домен с "отличившимся" кодом. Никакой другой полезной реакции от исключения в Dispose() ожидать не нужно. То, что горе-архитекторы (или не знаю, кто за это в МС отвечает) понаделали в WCF — это лишь загубили себе карму и нам её нужно исправлять, а не идти вслед за ними и гробить карму себе.
Help will always be given at Hogwarts to those who ask for it.
Re[7]: Исключение в констректоре и дестректоре
От: Аноним  
Дата: 19.07.10 04:12
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>То, что горе-архитекторы (или не знаю, кто за это в МС отвечает) понаделали в WCF — это лишь загубили себе карму и нам её нужно исправлять, а не идти вслед за ними и гробить карму себе.


речь про кидающий исключения Dispose у прокси, и, как следствие, "невозможность" исспользовать с ним using? о да, это был очень приятный сюрприз...
а вот интересно, это было чем то обусловлено или очевидный ляп который не хотят исправлять (обратная совместимость с багом ? кто мешает им в Dispose вызывать Abort в случае когда State == Faulted?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.