Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Потрепаться";
Текущий архив: 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
7-46520
amamed_3071
2002-01-18 13:28
2002.04.15
где находится файл?


6-46469
Lenidus
2002-02-03 15:29
2002.04.15
Как определить на какую ссылку вы нажали в окне Webbrowser?


3-46217
Alex1111
2002-03-22 14:33
2002.04.15
Sql


1-46298
Эдуард
2002-03-31 17:08
2002.04.15
Как получить резальтат работы внешней программы


6-46460
Diablo
2002-01-31 17:10
2002.04.15
вопрос по UDP





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский