Принцип работы конструторов
Вопросы к тем, кто понимает Делфи на глубоком уровне.
Вот у меня есть класс A, который унаследован от Tobject и класс В, который является потомком А. У них есть какие-то поля. В класс В добавлены поля.
1)Ни у А, ни у В не объявлены конструкторы. Что произойдет, если вызвать B.create? Какова будет цепочка вызовов?
2) У А объявлен и реализован конструтор. Что произойдет, если вызвать B.create? Какова будет цепочка вызовов? Будет ли вызван конструктор Tobject?
Заранее спасибо.
2) У А объявлен и реализован конструтор. Что произойдет, если вызвать B.create? Какова будет цепочка вызовов? Будет ли вызван конструктор Tobject?
1. Конструктор есть всегда, как и деструктор - если программист его не объявляет вручную, компилятор создаёт автоматически.
2. Если только a производный от TObject. И если в объявляемых конструкторах будет вставлен вызов конструктора базового класса: inherited Create;
Вообще-то, дефолтный конструктор родителя, о котором здесь и идёт речь, вызывается всякий раз при конструировании потомка. Сомневающиеся могут проверить, создав небольшую иерархию и производя в конструкторе запись какой-нибудь строки куда-нибудь в глобальную переменную (добавлением).
2. Если только a производный от TObject. И если в объявляемых конструкторах будет вставлен вызов конструктора базового класса: inherited Create;
Вот это в корне неверно. Объектная модель Delphi совсем непохожа на объектную модель C++. Ничего компилятор не создает автоматически. Утверждение, что конструктор есть всегда верно из-за того, что в Delphi все классы унаследованы от TObject, а в TObject конструктор есть.
Чтобы создать экземпляр класса, в случае отсутствия у него конструктора, вам придется вызвать один из (поскольку никто не запрещает создать несколько конструкторов, как, впрочем, и никто не заставляет называть их Create) конструкторов ближайшего класса-предка в иерархии, в котором есть конструктор (иначе вы попросту не сможете создать объект, поскольку синтаксис создания объекта предполагает явный указание имени конструктора).
Теперь о порядке вызова конструкторов.
Опять же здесь все не так как C++, где порядок вызова конструкторов неизменен TObject->A->B (в случае иерархии TObject<-A<-B). Delphi предлагает большую свободу. Небольшой пример номер раз:
type
B = class (A)
MyField: Integer;
constructor Create;
end;
constructor B.Create;
begin
MyField:=100; // Такое возможно - память уже выделена
inherited; // Меняем порядок вызова конструкторов
end;
type
B = class (A)
MyField1: Integer;
MyField2: Integer;
procedure MyMethod;
constructor Create;
end;
constructor B.Create;
begin
MyField1:=100;
MyMethod;
inherited; // ;)
MyField2:=MyField1 + 10;
end;
type
B = class (A)
MyField1: Integer;
MyField2: Integer;
function MyMethod: boolean;
constructor Create;
constructor OtherCreate;
end;
constructor B.Create;
begin
MyField1:=100;
if MyMethod then OtherCreate
else inherited;
MyField2:=MyField1 + 10;
end;
constructor B.OtherCreate;
begin
inherited Create;
MyField2:=MyField1 + 15;
end;
С деструкторами тоже самое.
Нихрена подобного. Всякая "либерализация" ведёт к увеличению бардака и нарушению нормальной работоспособности.
Хотя кое-что можно сделать и на С++, а то, что нельзя - просто ненужно.
Пример №1. Легко - через "список инициализации"
А вот примеры №2 и 3 легко могут привести к работе с неинициализированной областью памяти.
А вот примеры №2 и 3 легко могут привести к работе с неинициализированной областью памяти.
Память в Делфи выделяется до вызова Create и инициализируется Нулями. Всегда. Кроме того, всегда вызывается AfterConstruction после завершения работы любого конструктора.
Пардон, только после завершения всех конструкторов - точнее, после завершения конструктора указанного класса.
Тогда привести к обращению к нулевому указателю (ссылке), если вдруг (по какой-либое причине) вызовется метод базового класса :D
Хотя кое-что можно сделать и на С++, а то, что нельзя - просто ненужно.
Пример №1. Легко - через "список инициализации"
А вот примеры №2 и 3 легко могут привести к работе с неинициализированной областью памяти.
Видишь ли, это типичный ответ сишника :) Я и сам поначалу удивлялся такому поведению (сам изучал ООП на основе объектной модели C++), но потом в понял, что тут есть свои преимущества. Действительно, все эти возможности, которые я перечислил, зачастую приводят к трудноуловимым ошибкам, но при правильном использовании позволяют сделать очень интересные вещи...
К сожалению, при достаточной криворукости, даже объектная модель C++ не поможет :)
[quote=hardcase]Память в Делфи выделяется до вызова Create и инициализируется Нулями. Всегда. Кроме того, всегда вызывается AfterConstruction после завершения работы любого конструктора.[/quote]
При вызове любого конструктора у объекта уже инициализирована vmt, что позволяет в его теле вызывать виртуальные методы. Но и это еще не все :) Есть в Delphi такая штука, как классовые ссылки (фактически, ссылка на vmt класса, да к тому же конструктор в Delphi может быть виртуальным. Тут уж открываются такие возможности... :)