Форум: "Потрепаться";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 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;




Форум: "Потрепаться";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2002.04.15;
Скачать: [xml.tar.bz2];




Наверх





Память: 0.79 MB
Время: 0.037 c
1-46360           MAxiMum               2002-04-03 19:42  2002.04.15  
Pascal + ООП = ???


1-46320           ...                   2002-04-03 16:05  2002.04.15  
Ламерский вопрос


1-46396           ded_di                2002-04-02 13:05  2002.04.15  
печать rtf


3-46292           ol                    2002-03-26 12:50  2002.04.15  
Как в IB зделать select на select


4-46553           slydiman              2002-02-08 23:56  2002.04.15  
C++Builder & ExcelApplication