![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Стиль: Вызов виртуальных методов из деструкторов
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 не является строго обязательным, даже и в деструкторе.