Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Исключения в конструкторе и вызов деструктора

5.7K
24 ноября 2006 года
grizlyk
64 / / 10.04.2005
Доброе утро.

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

Есть такой класс
struct T
{
int *x;

void input()throw();

T(int *const ptr)throw():x(ptr){}
};

struct T1
{
int *a;
T b;
T2 c;

void print()const throw();

T()throw(exception&):a(0),b(0){ a=new int[10]; b.x=a;}
~T()throw(){ delete[] a; a=0; }
};

Вот его используем
try{ T obj; obj.b.input(); obj.print(); }catch(...){}

Вопросы:
1. Будет ли вызван деструктор, если произошла ошибка в инициализаторах конструктора T ( a(0),b(0),c() ), а конструктор, определенный программистом, выполняться не начинал;

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

Причина появления моего вопроса очень проста:
имеющаяся у меня информация не дает однозначного ответа на заданный мной вопрос о том, каковы правила вызова деструктора, если во время создания объекта (в конструкторе) выброшено исключение.

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

/*
a,b,c,d - члены объекта, которым я выделяю в констукторе память
*/
T()
//инициализатор
:a(0),d(a), b(0),c(0)
//конструктор
{
a=alloc_mem();
if(!a)goto no_a;
b=alloc_mem();
if(!b)goto no_b;
c=alloc_mem();
if(!c)goto no_c;

//полезный код конструктора
...
if(some error)goto all_allocated;
...
if(other error)goto all_allocated;
...
return;

//дублирую в конструкторе код
деструктора
all_allocated:
free_mem(c); с=0;
no_c:
free_mem(b); b=0;
no_b:
free_mem(a); a=0;
no_a:
throw;
}

//деструктор
~T()
{
free_mem(c); с=0;
free_mem(b); b=0;
free_mem(a); a=0;
}

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

/*
a,b,c,d - члены объекта, которым я выделяю в констукторе память
*/
T()
//инициализатор
:a(0),
d(a), //throw - здесь прервалась работа
конструктора
b(?),c(?) //неопределены
{
}

//деструктор
~T()
{
free_mem(c); с=0;
free_mem(b); b=0; //нельзя
free_mem(a); a=0; //нельзя
}

Третьи говорят что это зависит от реализации и стандартом не определено.

Однако, как трактует это все стандарт?
309
24 ноября 2006 года
el scorpio
1.1K / / 19.09.2006
Не знаю, как "стандарт", а в Builder'е я проверял это "методом научного тыка" :D

1. Если ошибка произошла при выделении памяти, то "вызываться" просто нечему (не проверял, потому что не мог сделать такую ошибку ;) )
2. Если ошибка произошла в "списке инициализации" класса: поведение создаваемого поля класса описывается, как для отдельного объекта, что же касается ранее созданных - не знаю :(
3. Если ошибка вообще произошла, то для базовых классов с завершёнными конструкторами деструкторы будут вызваны автоматически, в порядке, обратном вызову конструкторов, поля класса, в конструкторе которого произошла ошибка, также будут удалены корректно, а вот для создаваемых (new) объектов нужно будет прописывать delete вручную, в обработчике исключения конструктора.

Вывод, конструктор должен быть максимально простым, без генерации исключительных ситуаций. Если этого избежать невозможно (конструктор с параметрами, требующими проверки), то создание объектов класса нужно проводить после проверки.
63
24 ноября 2006 года
Zorkus
2.6K / / 04.11.2006
Стандарт я не скажу как трактует, но почитай "С++. Стандартная библиотека для профессионалов", Н. Джосъютис. Там обработка таких ситуаций в соответствии с современным стандартом рассмотрена. Может не очень подробно, но верно.
355
25 ноября 2006 года
<SCORP>
786 / / 21.10.2006
а вот так не выход:
Код:
struct T
{
  void *a;
  T() : a(0)
  {
    try
    {
       //something_which_can_throw_exception
       a = alloc_mem();
    }
    catch(...)
    {
       if (a)
         free_mem(a);
       throw something;
    }
  }
}

?
5.7K
25 ноября 2006 года
grizlyk
64 / / 10.04.2005
[QUOTE=<SCORP>;155852]а вот так не выход:[/QUOTE]Нет, для не учебной (рабочей) программы это не выход.

Во-первых, неудобно дублировать деструктор в конструкторе руками.

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

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

В-третьих, идея не выделять память в конструкторе, а выделять ее потом в (может быть виртуальных) методах create/destroy тоже не выход, т.к. неудобно будет использовать объекты, текст программы станет более трудночитаем.
355
25 ноября 2006 года
&lt;SCORP&gt;
786 / / 21.10.2006
Цитата: grizlyk

Во-первых, неудобно дублировать деструктор в конструкторе руками.


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

Цитата: grizlyk

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


именно поэтому указатель изначально проинициализирован нулём ещё до того, как может возникнуть исключительная ситуация

а вообще, я тоже думаю что такие вещи зависят от релизации компилятора. мне говорили, что на сегодня любой компилятор C++ реализует стандарт языка максимум на 80%. компилятор C++ от Micro$oft реализует его на 60% (наверное это было сказано ещё про 6 студию)

5.7K
26 ноября 2006 года
grizlyk
64 / / 10.04.2005
[QUOTE=<SCORP>;155897]выносим освобождение памяти в отдельный приватный метод[/QUOTE]Понятно, что можно сдалать и так, но радости в том, чтобы ловить исключения в конструкторе, мало.

[QUOTE=<SCORP>;155897]именно поэтому указатель изначально проинициализирован нулём ещё до того, как может возникнуть исключительная ситуация[/QUOTE]Кто же это по стандарту такой добрый, проинициализировал его нулем без меня?
309
26 ноября 2006 года
el scorpio
1.1K / / 19.09.2006
[QUOTE=grizlyk]Кто же это по стандарту такой добрый, проинициализировал его нулем без меня?[/QUOTE]
Не знаю - Builder наверное :) . В настройках проекта есть такой флаг.
А вообще, лучше самому списки инициализации писать.
Кстати, порядок объектов-полей в списке роли не имеет. Ведь ошибки "выделения памяти" для поля объекта быть не может - объект создаётся в уже выделенной области памяти. А конструктор должен быть таким, чтобы исключения не генерировались - ввод "опасных" данных в принадлежащие объекты нужнопроводить внутри конструктора.
5.7K
26 ноября 2006 года
grizlyk
64 / / 10.04.2005
Цитата: el scorpio
Не знаю - Builder наверное :) . В настройках проекта есть такой флаг.

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

Цитата: el scorpio
Кстати, порядок объектов-полей в списке роли не имеет.

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

Цитата: el scorpio
Ведь ошибки "выделения памяти" для поля объекта быть не может - объект создаётся в уже выделенной области памяти.

Вот это я не понял, если объект, вложенный в другой объект, имеет динамическую память (большинство сложных объектов такие), то он должен ее выделять себе сам и может бросить исключение. Да мало ли других причин, по которым он сожет бросить исключение? Исключение по причине нехватки памяти осложнено еще и утечкой памяти.

Цитата: el scorpio
А конструктор должен быть таким, чтобы исключения не генерировались - ввод "опасных" данных в принадлежащие объекты нужнопроводить внутри конструктора.

Это (ввод "опасных" данных...) практически невозможно.

1. Если попытаться прямо поручить выделение памяти "самому последнему, реально создающемуся объекту", то это нарушает инкапсуляцию, смысл создания объекта.

2. Если создать виртуальный интерфейс create/destroy и вызвать всю инициализацию, то это для простых объектов неудобно, да и вообще, это явно попытка избежать использования конструктора/деструктора только потому, что их поведение непонятно.

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

355
26 ноября 2006 года
&lt;SCORP&gt;
786 / / 21.10.2006
Цитата: grizlyk
Кто же это по стандарту такой добрый, проинициализировал его нулем без меня?



посмотри внимательно в код и увидишь там инициализацию в конструкторе структуры ;)

309
27 ноября 2006 года
el scorpio
1.1K / / 19.09.2006
[QUOTE=grizlyk]Вот это я не понял, если объект, вложенный в другой объект, имеет динамическую память (большинство сложных объектов такие), то он должен ее выделять себе сам и может бросить исключение.
Да мало ли других причин, по которым он сожет бросить исключение? Исключение по причине нехватки памяти осложнено еще и утечкой памяти.
[/quote]
1. Не учёл :(
2. Но вот "конструктор по умолчанию" по другим причинам бросить исключение не может - в противном случае, это очень плохой конструктор.

Цитата:

1. Если попытаться прямо поручить выделение памяти "самому последнему, реально создающемуся объекту", то это нарушает инкапсуляцию, смысл создания объекта.

2. Если создать виртуальный интерфейс create/destroy и вызвать всю инициализацию, то это для простых объектов неудобно, да и вообще, это явно попытка избежать использования конструктора/деструктора только потому, что их поведение непонятно.


1. VCL позволяет реализовать такую возможность, используя переопределение методов AfterConstruction и BeforeDestruction класса TObject - но это делается крайне редко, в основном эти методы используются для выполнения действий, которые должны происходить при создании/удалении, но выполняться над данными производных классов.
2. Как такое реализовать без TObject, мне не известно :(, потому что внутри конструктора/деструктроа виртуализация не работает.

5.7K
05 декабря 2006 года
grizlyk
64 / / 10.04.2005
Оказалось, что по крайней мере на настоящий момент автоматический вызов деструктора не поддерживается компилятором, если объект не был успешно создан, т.е. если конструктор бросил исключение, то деструктор не будет вызван.

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

class T
{
...
protected:
do_destruct()throw(){/* здесь код деструктора */}

public:
virtual ~T()throw(){do_destruct();}

T()throw(exception&):/* здесь код инициализации переменных - членов класса */
{try{
/* здесь код конструктора */
}catch(...){do_destruct(); throw;}}

};

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

В принципе, выбор вызова/невызова деструктора компилятором в какой то мере произволен, т.е. каждый вариант поведения имеет как плюсы, так и минусы.
309
06 декабря 2006 года
el scorpio
1.1K / / 19.09.2006
Частный случай - класс не имеет "родителей".
Если бы сласс T был бы производным от класса A, то, в случае исключения в конструкторе T, деструктор для A вызвался бы автоматически.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог