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

Reply via email to