Hi Jack,
Take the following class definition:
TBase = class(TObject)
private
FButton: TButton;
FNumber: Integer;
public
constructor Create;
destructor Destroy; override;
procedure Dump; virtual;
end;
It contains a field named FButton and a field named FNumber. You refer to
these as variables, but technically their called fields.
Because FButton is of type TButton which is a class type; FButton is really
a pointer and is used to store the address of a TButton object, once that
object has been instantiated. FNumber on the other hand is a simple type (an
integer in this case) and as such FNumber stores the data directly.
The amount of memory that an object directly occupies is defined by the
number and size of the various fields that make up that object. Pointers and
Integers occupy 4 bytes of memory each, so an instance of TBase as shown
above occupies 8 bytes of memory. In reality, every object also contains a
hidden field called the "VMT Pointer". It's the 1st field in the object and
is used internally by Delphi to keep tract of virtual method pointers.
(That's a subject by itself). Suffice it to say that an instance of TBase
would directly occupy 12 bytes in memory.
Because the "footprint" occupied by an object is a characteristic of the
class definition, TObject (from which all classes are derived) includes a
static method (InstanceSize) which returns the size in bytes of each
instance of the object type and because its static, you can call it without
instantiating the object as in:
writeln('TBase.InstanceSize='+IntToStr(TBase.InstanceSize));
returns:
TBase.InstanceSize=12
Constructors:::
While an instance of TBase contains a 4 byte pointer to a TButton object by
defintion, the TButton instance must be explicitly created. Until that
occurs, FButton will contain its default value of $00000000 (or nil). That's
where the constructor typically comes in. While the compiler will allocate
the 12 bytes of memory required to store an TBase object, you must provide
additional code to create the TButton instance. That would typically be done
in the constructor as in:
Constructor TBase.Create;
Begin
FButton := TButton.Create(nil);
End;
While I said that TBase directly occupies 12 bytes of memory, if the above
constructor where executed, the TBase instance would physically consume not
12 bytes but instead 548 bytes. That's because a TButton object directly
occupies 536 bytes, so you have 12 + 536 = 548.
If you then assume the following class:
TDerived = class(TBase)
Private
FAge: Integer;
Public
Constructor Create;
End;
TDerived.InstanceSize = 16
A TDerived instance is 16 bytes because it contains all of the fields in
TBase (12) plus an additional 4 bytes for FAge.
If its constructor looked like this:
Constructor TDerived.Create;
Begin
Inherited;
FButton.Caption := 'Press Me';
FAge := 100;
End;
The call to inherited calls TBase.Create which would allocate the TButton
instance and assign it to FButton.
If instead it looked like this:
Constructor TDerived.Create;
Begin
FButton.Caption := 'Press Me';
FAge := 100;
End;
TBase.Create will not be called, TButton will not be created and the value
of FButton will rename nil.
An access violation would result because FButton does not point to a valid
TButton instance.
In some cases it's ok to not call the constructor. For example, TBase.Create
technically should be coded as:
Constructor TBase.Create;
Begin
Inherited;
FButton := TButton.Create(nil);
End;
Because TBase is derived from TObject and TObject has a constructor, but
because it looks like:
constructor TObject.Create;
begin
end;
which does nothing, not calling it does no harm. But in general you should
always call the inherited constructor unless you know that its safe not to.
The same rules apply to the destructor. If we had:
Destructor TBase.Destroy;
Begin
FButton.Free;
End;
Constructor TDerived.Create;
Begin
Inherited;
FButton.Caption := 'Press Me';
FAge := 100;
End;
Destructor TDerived.Destroy;
Begin
FAge := 0;
End;
This would cause the 12 byte footprint occupied by the TDerived instance to
be released, but because TBase's destructor was not called, the 536 byte
TButton object would still exist in memory. Because the destruction of the
TDerived instance effectively erases the value of FButton, the object
FButton was pointing is now orphaned and its memory will not be reclaimed
until the application terminates. This is what we call a memory leak.
Take a look at the attached code for an example. See that FButton remains
nil in TDerivedNoInherit because TBase.Create is not called.
C:\Projects\ClassSize>inherit
TBase Object (InstanceSize=12):
FButton=008C1EC0
TDerivedNoInherit Object (InstanceSize=16):
FButton=00000000
FEdit=008C1F00
TDerivedWithInherit Object (InstanceSize=16):
FButton=008C1F3C
FEdit=008C21E4
-----Original Message-----
From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] On Behalf
Of Jack
Sent: Thursday, April 13, 2006 1:00 PM
To: Borland's Delphi Discussion List
Subject: Re[2]: Class constructor and destructor
Hello Rob,
Thanks for the fast reply.
> When you call a constructor to create an object, all the memory for that
> object gets allocated and initialized before _any_ line of code runs
> from any of the class's constructors. The object is fully created by the
> time any constructor runs.
So the variables of the base class will also have been created even
before the constructor of the base class is called?
--
Best regards,
Jack
_______________________________________________
Delphi mailing list -> [email protected]
http://www.elists.org/mailman/listinfo/delphi
program Inherit;
{$APPTYPE CONSOLE}
uses
SysUtils,
StdCtrls;
type
TBase = class(TObject)
private
FButton: TButton;
FNumber: Integer;
public
constructor Create;
destructor Destroy; override;
procedure Dump; virtual;
end;
TDerivedNoInherit = class(TBase)
private
FEdit: TEdit;
public
constructor Create;
destructor Destroy; override;
procedure Dump; override;
end;
TDerivedWithInherit = class(TBase)
private
FEdit: TEdit;
public
constructor Create;
destructor Destroy; override;
procedure Dump; override;
end;
var
FBase: TBase;
{ TBase }
constructor TBase.Create;
begin
FButton := TButton.Create(nil);
end;
destructor TBase.Destroy;
begin
FreeAndNil(FButton);
end;
procedure TBase.Dump;
begin
writeln(Format('FButton=%p', [Pointer(FButton)]));
end;
{ TDerivedNoInherit }
constructor TDerivedNoInherit.Create;
begin
FEdit := TEdit.Create(nil);
end;
destructor TDerivedNoInherit.Destroy;
begin
FreeAndNil(FEdit);
end;
procedure TDerivedNoInherit.Dump;
begin
inherited;
writeln(Format('FEdit=%p', [Pointer(FEdit)]));
end;
{ TDerivedWithInherit }
constructor TDerivedWithInherit.Create;
begin
inherited;
FEdit := TEdit.Create(nil);
end;
destructor TDerivedWithInherit.Destroy;
begin
inherited;
FreeAndNil(FEdit);
end;
procedure TDerivedWithInherit.Dump;
begin
inherited;
writeln(Format('FEdit=%p', [Pointer(FEdit)]));
end;
begin
writeln(Format(#10'TBase Object (InstanceSize=%d):', [TBase.InstanceSize]));
with TBase.Create do
try
Dump;
finally
Free;
end;
writeln(Format(#10'TDerivedNoInherit Object (InstanceSize=%d):',
[TDerivedNoInherit.InstanceSize]));
with TDerivedNoInherit.Create do
try
Dump;
finally
Free;
end;
writeln(Format(#10'TDerivedWithInherit Object (InstanceSize=%d):',
[TDerivedWithInherit.InstanceSize]));
with TDerivedWithInherit.Create do
try
Dump;
finally
Free;
end;
end.
_______________________________________________
Delphi mailing list -> [email protected]
http://www.elists.org/mailman/listinfo/delphi