Форум: "Потрепаться";
Текущий архив: 2002.04.15;
Скачать: [xml.tar.bz2];
ВнизDelphi: конструктор и деструктор Найти похожие ветки
← →
Shaman_Naydak (2002-03-06 12:49) [0]Тут у мнея одна мыслишка появилась по поводу конструкторов и деструкторов в Дельфях при обсуждении
http://delphi.mastak.ru/cgi-bin/forum.pl?look=1&id=1015335325&n=3
а именно:
Как известно, для того чтобы создавать объект корректно одним и тем же конструктором независимо от класса (предок/наследник) конструктор необходимо объявить виртуальным (см. TComponent).
И то, что это возможно, сильно облегчает жизнь Delphi"stam по сравнению с C++ (Кстати, я категорически против устраивания в этой ветке флейма Delphi vs C++ !!!! Допустимы только сухие высказывания и только в том случае, если четко знаешь как реализована та или иная вещька в ОБЕИХ системах).
Итак, вернемся к нашим баранам..
Но, если не ошибаюсь, в третьих Delph"ях у TObject"a появились виртуальные AfterConstruction & BeforeDestruction!
=> Если выделение памяти и освобождение объектов перенести из конструкторов/деструкторов в эти методы, то необходимость в виртуальных конструкторах в принципе отпадает, и любой объект можно будет создать корректно TObject"овским Create.
=> Возникает следующая идея. TObject"овский Create - конструктор по умолчанию. Все остальные конструкторы используются для передачи параметров.
В сочетании же с цепочкой классов (см.вышеуказанную ветку) это дало бы возможность в Run-time корректно создать любой! объект.
А вы как думаете?
← →
Shaman_Naydak (2002-03-06 13:01) [1]Кстати, хочу кинуть абстрактный пример, иллюстрирующий мою мысль, да и правила создания/уничтожения объектов.
Заранее прошу прощения за возможные описки. код набирался прямо в броузере.
type
TTestObj = class
private
FStrings: TStringList;
public
constructor CreateParam(Param: string);
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
end;
constructor TTestObj.CreateParam(Param: string);
begin
FStrings:=TStrinList.Create;
FStrings.Add(Param);
end;
procedure TTestObj.AfterConstruction;
begin
// Проверка на то, что объект можно создавать.
// Если нельзя - вызвать исключение
if not SkyIsBlue then
Abort;
inherited;
if FStrings = nil then
FStrings:=TStringList.Create;
end;
procedure TTestObj.BeforeDestruction;
begin
// Проверка на то, что объект можно уничтожать.
// Если нельзя - вызвать исключение
if SkyIsBlue then
Abort;
inherited;
FStrings.Free;
end;
← →
vuk (2002-03-06 13:11) [2]Теоретически возможно все. Это я уже писал в какой-то ветке. :o)
Все Ваши размышления совершенно справедливы, кроме одного - удаление при таком подходе можно оставить и в деструкторе - он и так виртуальный. К тому же часто BeforeDestruction может использоваться для управления временем жизни экземпляра(как у TInterfacedObject). Да и концептуально это немного не верно все-таки вся инициализация должна происходить именно в конструкторе.
Более правильным подходом, на мой взгляд, было бы введение в TObject виртуального конструктора. Но это все теоретически - практически возможности переделать TObject отсутствует.
← →
Mystic (2002-03-06 13:38) [3]
> => Если выделение памяти и освобождение объектов перенести
> из конструкторов/деструкторов в эти методы, то необходимость
> в виртуальных конструкторах в принципе отпадает, и любой
> объект можно будет создать корректно TObject"овским Create.
1) Ряду виртуальных конструкторов требуются параметры.
2) При TObject.Create происходит неявный вызов GetMem с размером объекта. В твоем примере память под поле FStringList выделена не будет.
← →
Shaman_Naydak (2002-03-06 14:08) [4]>> Mystic
1. Я не против использования виртуальных конструкторов. Я за то, чтобы по умолчанию тоже все объекты тожы создавались корректно.
2. Зуб даю - все работать будет замечательно. А что, проверить сложно? Я так понимаю - это намек, что я в конструкторе забыл вызвать inherited Create; ?
Выделение памяти под объект происходит перед вызовом конструктора. Фактически в классах (class) конструктор - это метод, вызывающийся после выделения памяти и ее зачистки, в отличии от паскалевской модели (object).
Компилятор видит, что вызывается метод, который называется конструктор, сильно радуется, выделяет память, зануляет и вызывает наконец конструктор. Вызов предка нужен, только если он что-нидь делает.. а у TObject"a конструктор пустой..
Хотя лучше конечно вызвать, а вдруг предка поменяете?
← →
Digitman (2002-03-06 14:55) [5]>Mystic
>>"При TObject.Create происходит неявный вызов GetMem с размером объекта"
Утверждение не соответствует действительности. Память под экземпляр объекта выделяется (и следом же - инициализируются служебные поля экз-ра) перед вызовом виртуального конструктора. Задача конструктора - инициализация пользовательских (определенных в классе-наследнике и не относящихся к служебным) полей.
← →
Алексей Петров (2002-03-06 15:28) [6]> Digitman © (06.03.02 14:55)
> Память под экземпляр объекта выделяется (и следом же - инициализируются служебные поля экз-ра) перед вызовом виртуального конструктора.
слово "виртуального" - лишнее. Любого конструктора.
← →
Vuk (2002-03-06 15:32) [7]>Выделение памяти под объект происходит перед вызовом
>конструктора.
Вызов происходит ИЗ конструктора.
>Компилятор видит, что вызывается метод, который называется
>конструктор, сильно радуется, выделяет память, зануляет и
>вызывает наконец конструктор.
Не совсем так, точнее совсем не так. Компилятор встраивает в конструктор специальный код, который, который управляет выделением памяти, причем выделение памяти делается не всегда, а только тогда, когда self=nil (иногда может быть и не так). Точно также встраивается код, который вызывает AfterConstruction.
← →
Digitman (2002-03-06 15:37) [8]>Vuk
>>"Вызов происходит ИЗ конструктора"
Вызов - чего ? GetMem ? Где ты это увидел ?
← →
vuk (2002-03-06 15:48) [9]>Вызов - чего ? GetMem ? Где ты это увидел ?
Не, не GetMem, а процедуры _ClassCreate. Хотя, в конечном итоге, там среди прочего и GetMem вызывается (в методе NewInstance).
← →
Digitman (2002-03-06 15:59) [10]>vuk
Под "конструктором" мы ведь подразумеваем тело "конструирующего" метода Create ! Компилятор вставляет код его вызова уже после кода, отвечающего за распределение памяти под экз-р и инициализацию. Т.о., некорректно говорить, что вызов встроенного компилятором кода создания экз-ра объекта происходит в момент выполнения метода-конструктора.
← →
Vuk (2002-03-06 16:32) [11]to Digitman:
Ну с этим-то никто не спорит. Но если уж речь заходит об изменении порядка конструирования то нужно все тонкости учитывать.
← →
VuDZ (2002-03-06 17:01) [12]2Shaman_Naydak
я вот только одного не понял, как можно переписав под себя выделение памяит использовать это для динамического сздания классов по имени?
Я вижу только 2 реальных метода:
1. класс регистратор, в котором храняться все имена классов
2. обратное наследование, когда потомок указывает на одного или нескольких предков.
← →
vuk (2002-03-06 17:20) [13]to VuDZ:
Здесь дело вот в чем. У TObject нет виртуального конструктора, поэтому при существующем положении вещей невозможно создать экземпляр _абсолютно_ любого класса. И происходит это именно потому, что данные экземпляра принято инициализоровать в конструкторе (что есть правильно). Автор предлагает делать это не в конструкторе, а в методе AfterConstruction. В некоторых случаях это вполне приемлемое решение.
← →
VuDZ (2002-03-06 17:34) [14]я не совсем понял, но что мешает сделать так:
TObject -> TParent -> TChild
var
TObject obj;
begin
jbj := (TObject)TChild.Create();
т.е. создавать потомков и приводить их к типу TObject или любому другому предку?
> поэтому при существующем положении вещей невозможно создать
> экземпляр _абсолютно_ любого класса
А ведь никогда и не удасться сделать абсолютно любой класс - только произвлдный от некоторого, так как параметры констукрота могут отличаться.
>Автор предлагает делать это не в конструкторе, а в методе AfterConstruction.
Мысль интересная, но вот только безнадёжная: не так уж и много классов создаётся с пустым конструктором - в нём могут создаваться какие-то структуры и пр.
Это кончно можно вынести в другой метод, но, по-моему, это не очень хорошая мысль.
> В некоторых случаях это вполне приемлемое решение.
да, но только в некоторых, а речь идёт о создние абсолютно любого класса.
Ведь я могу и в operator new инициализировать данные, но только зачем?
← →
Shaman_Naydak (2002-03-06 17:39) [15]>> Vuk
Вы правильно меня поправили, я не совсем правильно расписал из желания упростить. Но и вас можно тоже поправить :)
Там нет проверки на self = nil.
просто дельфи перед вызовом конструктора
загоняет в eax - ссылка на класс (в смысле TClass),
а в , ВНИМАНИЕ, DL = 1 <-!!
и вызывает констуктор.
Да, в каждый конструктор дельфей встраивается код, который первым делом проверяет, а не равен ли dl = 0? Если не равен, стало быть этот конструктор первый в списке и надо делать вещки, а именно вызов vmtNewInstance. Это обычно NewInstance TObject"a.
Тот вызывает vmtInstanceSize (обычно InstanceSize TObject"a),
а затем вызывается GetMem..
Ну и естественно, при вызове вложенного конструктора Dl = 0 и ситуация не срабатывает.
В конце конструктора тоже встраивается код, который при dl <>0
вызывает vmtAfterConstruction (AfterConstruction)
Запарился я писать.. В общем, аналогично и для деструктора!
← →
vuk (2002-03-06 18:31) [16]to Shaman_Naydak:
>Но и вас можно тоже поправить :)
Согласен, не помню я наизусть исходники RTL. :o) Я помню только, что там проверка есть.
>Если не равен, стало быть этот конструктор первый в списке и
>надо делать вещки, а именно вызов vmtNewInstance. Это обычно
>NewInstance TObject"a.
Есть еще один тонкий случай. Это когда конструктор вызывается повторно.
Пример:
Instance := TSomeClass.Create(...);
...
Instance.Create(...);
В этом случае в EDX прописывается значение $FFFFFFFF.
То есть конструктор может быть использован не только для создания экземпляра, но и для для приведения данных существующего экземпляра в исходное состояние.
← →
Shaman_Naydak (2002-03-06 18:58) [17]>> vuk
Не знал, не знал. Деструктор,я надеюсь, при этом вызывается?
>> VUDZ
А вы, батенька, явно же на с++ пишите, эта конструкция с головой вас выдает :)
> jbj := (TObject)TChild.Create();
Не нужно это все на Дельфях, просто пишем
jbj := TChild.Create(), ибо преобразование в парента разрешено.
Я в общем-то не предлагал всем срочно отменить конструкторы, тем более с параметрами, а просто предложил в AfterConstruction проверять на создание служебных объектов, и если они не созданы - создать их. Таким образом TObject"овский Create получится конструктором по умолчанию. Ну уж Вам, как человеку, пишущему на С++ не надо объяснять зачем нужен конструктор по умолчанию :)
Как справедливо заметил vuk - это иногда удобно.. Хотя в Дельфях опять же это все не сильно актуально из-за тех же виртуальных конструкторов.
← →
VuDZ (2002-03-06 19:21) [18]ой, дейтсвительно - пишу на C++ :>
Честно говоря, это просто страдание фигнёй - практического смысла 0, реализуемость - ну незнаю, Вам решать.
Среди всех задач, которые мне приходилось решать по работе, было подобное, но всё это решается гораздо проще
← →
vuk (2002-03-06 19:25) [19]>Деструктор,я надеюсь, при этом вызывается?
Нет. И не должен, поскольку это не создание экземпляра, а особый случай вызова конструктора - преринициализация. Просто если Вы хотите, чтобы Ваш класс работал таким образом, нужно учитывать это при написании конструктора. То есть:
cosntructor TSomeClass.Create( ... );
begin
inherited Create(...);
if SomeList = nil then
SomeList := TList.Create
else SomeList.Clear;
....
end;
← →
Shaman_Naydak (2002-03-06 19:48) [20]>> vuk
Ага. А я уж было подумал, что фишка только в том, чтобы перераспределение памяти под объект не вызывать.. хотя сколько ее-то распределяется
← →
vuk (2002-03-06 20:23) [21]На самом деле это все, конечно, побочный эффект возможности отделить операцию выделения памяти для экземпляра от его инициализации (такое тоже может иногда потребоваться).
Вот, например, фрагмент кода из TReader.ReadComponent
Result := TComponent(ComponentClass.NewInstance);
if ffInline in Flags then
begin
Include(Result.FComponentState, csLoading);
Include(Result.FComponentState, csInline);
end;
try
Result.Create(Owner);
except
Result := nil;
raise;
end;
Страницы: 1 вся ветка
Форум: "Потрепаться";
Текущий архив: 2002.04.15;
Скачать: [xml.tar.bz2];
Память: 0.52 MB
Время: 0.005 c