Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Стиль: Вызов виртуальных методов из деструкторов


Страницы:   1 2 3

Igorek ©   (2002-10-24 13:45:00) 

Поясню на примере: есть класс A, от него наследуется класс B. В классе А есть виртуальная ф-ция f, которая переопределена в насленике В. Причем переопределенный вариант работает с полями, которые есть в классе В. А в деструкторе класса А она вызывается. Теперь представим уничтожение класса В. Сначала очищаются поля в своем классе, потом вызывается деструктор предка (и через него функция f соответственно). И поскольку она виртуальная, то вызовется переопределенный вариант из В, который обратиться к уже очищенным полям из В.
Варианты выхода из ситуации:
1) избегать прямого или косвенного вызова виртуальных функций из деструкторов вообще (а для этого иногда дублировать код в деструкторах)
2) давать этим функциям знать, не из деструктора ли они вызываются (ввести в класс флажок Destroing или добавить флажок параметр CalledFromDestructor)
3) разделять функциональность ф-ции f на ту, которая выполняется всегда, и на ту, которая выполняется только при вызове не из деструктора; т.е. еще две функции, первую из которых и вызывать из деструктора; но может случиться, что f придется разделить более чем на две части (если они идут непоочередно).

Вот такой код на Паскале:

A = class
procedure f; virtual;
destructor Destroy; virtual;
end;
B = class(A)
procedure f; override;
destructor Destroy; override;
end;

destructor A.Destroy;
begin
f;
end;

destructor B.Destroy;
begin
inherited;
end;

Если компилировать и трассировать на Делфи 6, то увидим, что при уничтожении обьекта класса В, порядок вызовов будет такой
-В.Destroy
-A.Destroy
-B.f

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


Igorek   (2002-10-29 19:11:00) [21]

А последовательность вызова конструкторов/деструкторов зато можно сохранить. И вообще в них только память выделять/освобождать, а инициализировать поля в отдельных методах: если есть необходимость поменять порядок или функциональность - просто сделать.


Igorek   (2002-10-29 19:07:00) [22]

2 MBo © (29.10.02 18:57)

> например, в родителе было только Height:=100, а в потомке
> Height:=200

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

Короче вариантов как всегда масса...


MBo   (2002-10-29 18:57:00) [23]

>вызовы... обязательны, и порядок их строго определен
>мы обязаны писать вначале конструктора и в конце деструктора inherited.
Это по меньшей мере разумно. Обращение в конструкторе с новыми, введенными нами в наследнике полями может зависеть от того, созданы ли старые. Наоборот - может потребоваться очень редко, поэтому в большинстве случаев обычный порядок правилен. Если же в конструкторе родителя ничего не делается, либо мы изменяем то,что там делалось (например, в родителе было только Height:=100, а в потомке Height:=200), нет смысла его вызывать.


Igorek   (2002-10-29 18:37:00) [24]

2 Юрий Зотов © (25.10.02 12:17)
Странно. Я лично привык считать, что вызовы конструкторов и деструкторов обязательны, и порядок их строго определен. Для конструкторов - от предков к наследникам, для деструкторов - наоборот. Т.е. мы обязаны писать вначале конструктора и в конце деструктора inherited.


Ученик   (2002-10-25 22:40:00) [25]

>Anatoly Podgoretsky © (25.10.02 22:28)
Это был законченый пример, о каких других конструкторах идет речь ?


Anatoly Podgoretsky   (2002-10-25 22:28:00) [26]

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


Юрий Зотов   (2002-10-25 22:11:00) [27]

> vuk © (25.10.02 22:00)

В подобных случаях я УМЫШЛЕННО не делаю проверок. Считаю, что пусть лучше вылетит Exception и послужит сигналом к тому, что где-то в программе что-то не так. Иначе ошибка может оказаться замаскированной, а такие ловить значительно сложнее.


vuk   (2002-10-25 22:00:00) [28]

>Видимо, Анатолий имел в виду, что если в TB.Create нет вызова
>inherited, то вызов inherited в TB.Destroy не только не нужен,
>но и даже приведет к ошибке.
IMHO при грамотнонаписанных деструкторах это безразлично. То есть если в деструкторе делается попытка освобождения какого-либо ресурса, то должна быть проверка того, что именно мы пытаемся освободить, и если не захватывали ресурсы, то и освобождать нечего.
А вот при невызове конструктора предка вероятность получить неверно работающий экземпляр будет очень велика...


Юрий Зотов   (2002-10-25 21:31:00) [29]

Видимо, Анатолий имел в виду, что если в TB.Create нет вызова inherited, то вызов inherited в TB.Destroy не только не нужен, но и даже приведет к ошибке.


Ученик   (2002-10-25 21:06:00) [30]

>Anatoly Podgoretsky © (25.10.02 19:53)
Представил, в чем вопрос ?


Anatoly Podgoretsky   (2002-10-25 19:53:00) [31]

Вот у тебя есть
destructor TB.Destroy;
begin

end;

попробуй представить
constructor TB.Create;
begin
end;


Ученик   (2002-10-25 19:29:00) [32]

>Anatoly Podgoretsky © (25.10.02 19:27)

???


Anatoly Podgoretsky   (2002-10-25 19:27:00) [33]

Ученик © (25.10.02 12:41)
Неочевидно, вот если приведешь текст конструкторов тогда можно будет сказать одназначно


Ученик   (2002-10-25 19:01:00) [34]

>Юрий Зотов © (25.10.02 18:58)
Спасибо за ответ, теперь будем надеяться, все поняли правильно :-)


Юрий Зотов   (2002-10-25 18:58:00) [35]

> Толик © (25.10.02 12:47)

И в невиртуальных тоже.


> Ученик © (25.10.02 12:41)

Замечательный пример. Правда, ничто не мешает гарантированно освободить память в другом методе, и тогда деструктор становится вообще не нужен. Ни inherited, ни даже собственный.

Вот еще один пример, более явный. И без всяких деструкторов.

procedure KillMem;
var
P: pointer;
begin
GetMem(P, ...)
end;

Так тоже можно. Но тоже не нужно. И подобных примеров - море. Как говорится, если уж человек написал ТАКОЕ, то это надолго. Хоть с деструктором, хоть без него.

Я говорил, что вызывать inherited не обязательно. И это так. Но разве я говорил, что не обязательно ДУМАТЬ?


Ученик   (2002-10-25 12:50:00) [36]

>Толик © (25.10.02 12:47)
Поэтому, я и попросил Юрия Зотова пояснить, что он имеет ввиду :-)


vuk   (2002-10-25 12:50:00) [37]

to VaS:
>Деструктор в OP - не совсем то же, что и в С++. Воспринимай его
>как событие OnDestructing (но не OnDestructed) :)
Почти, но не совсем так. Как OnDestructing лучше воспринимать метод BeforeDestruction, т.к. в этом методе еще можно отменить разрушение экземпляра (если сгенерировать исключение), а вот если уж попали в деструктор, то обратного пути уже нет.

to Ученик:
>Что Вы имеете ввиду ?
Если в деструкторах предков не производится никаких действий, освобождающих ресурсы или изменяющих какие-либо глобальные переменные, говоря проще, деструкторы предков всех предков пусты, то их вызов необязателен. К примеру, не обязателен вызов деструктора, если предком является TObject. Правда, дело в том, что зачастую невозможно узнать что делается в деструкторе предков, поэтому лучше явно делать вызов деструктора предка. Дополнительно это увеличивает совместимость кода наследника с возможными будущими изменениями в коде предка.


Толик   (2002-10-25 12:47:00) [38]

to Ученик © (25.10.02 12:41)
Так можно, но не нужно. :) Очевидно, что память теряется.
А вот насчёт того, что в виртуальных ф-ях inherited не обязателен Юрий Зотов © (25.10.02 12:17) абсолютно прав. Попробуйте объявить в родительском классе ф-ю, объявленную как virtual abstract, а затем в дочернем классе вызвать inherited. Вызывать inherited или нет это сугубо личное дело: если надо - вызываете, если не надо - то нет...


Ученик   (2002-10-25 12:41:00) [39]

>Юрий Зотов © (25.10.02 12:38)

Так можно ?

type
TA = class
private
FP : PChar;
public
constructor Create;
destructor Destroy; override;
end;

TB = class(TA)
destructor Destroy; override;
end;

constructor TA.Create;
begin
inherited Create;
GetMem(FP, 1000);
end;

destructor TA.Destroy;
begin
FreeMem(FP);
inherited Destroy;
end;

destructor TB.Destroy;
begin

end;

procedure TForm1.Button1Click(Sender: TObject);
begin
TB.Create.Free
end;


Юрий Зотов   (2002-10-25 12:38:00) [40]

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


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо