views:

121

answers:

1

Yet another in my series of questions regarding constructors in Delphi.

i have a base class that has has the virtual constructor:

TComputer = class(TObject)
public
    constructor Create(Teapot: Integer); virtual;
end;

The constructor is virtual for the times that someone needs to call

var
   computerClass: class of TComputer;
   computer: TComputer;
begin     
   computer := computerClass.Create(nTeapot);

The constructor is overridden in descendants:

TCellPhone = class(TComputer) 
public
   constructor Create(Teapot: Integer); override;
end;

TiPhone = class(TCellPhone ) 
public
   constructor Create(Teapot: Integer); override;
end;

Where TCellPhone and TiPhone descendants each have their opportunity to do their own initialization (of members not included for readability).

But now i add an overloaded constructor to some ancestor:

TCellPhone = class(TComputer) 
public
   constructor Create(Teapot: Integer); override; overload;
   constructor Create(Teapot: Integer; Handle: string); overload;
end;

The alternate constructor in TCellPhone calls the other virtual constructor, so it always gets the proper overridden behaviour:

constructor TCellPhone.Create(Teapot: Integer; Handle: string);
begin
   TCellPhone.Create(Teapot); //call sibling virtual constructor

   FHandle := Handle;
end;

The problem is that the descendant, overridden, constructor is never called. The actual stack trace chain of calls is:

phone := TiPhone.Create(37, 'spout')
   constructor TCellPhone.Create(Teapot: Integer; Handle: string)
      constructor TCellPhone.Create(Teapot: Integer)
         constructor TComputer.Create(Teapot: Integer)
            TObject.Create

The sibling call to TCellPhone.Create(int), which is virtual, should have called the descendant, overridden, method in TiPhone:

phone := TiPhone.Create(37, 'spout')
   constructor TCellPhone.Create(Teapot: Integer; Handle: string)
      constructor TiPhone.Create(Teapot: Integer)
         constructor TCellPhone.Create(Teapot: Integer)
            constructor TComputer.Create(Teapot: Integer)
               TObject.Create

So it seems that attempts to use a sibling virtual constructor is Delphi do not work as expected.

Is it then a bad idea for one constructor to use another? Is the design intention that code in overloaded constructors be copy-paste versions of each other?

i notice in .NET that some constructors chain to each other:

public Bitmap(int width, int height) : this(width, height, PixelFormat.Format32bppArgb) {}

public Bitmap(int width, int height, PixelFormat format) {...}

This only seems to be a problem if:

  • a constructor is virtual
  • you overload the constructors

Is the rule that you cannot have one constructor overload another?

+8  A: 

Errr..

constructor TCellPhone.Create(Teapot: Integer; Handle: string);
begin
   TCellPhone.Create(Teapot); //call sibling virtual constructor

   FHandle := Handle;
end;

That should be:

constructor TCellPhone.Create(Teapot: Integer; Handle: string);
begin
   Create(Teapot); //call sibling virtual constructor

   FHandle := Handle;
end;

You were just creating a new TCellphone instance and not calling the other Create method.

The_Fox
+1. I was right about to answer this, but you beat me to it.
Mason Wheeler
+1, leaking a CellPhone!
Sertac Akyuz
Well thank you. i just thought that `inherited Create` calls the ancestor - i *must* have to have something to indicate *me*. :\
Ian Boyd
The thing that indicates *me* is `Self`, @Ian: `Self.Create(Teapot)`. As usual, `Self` is implied; you needn't mention it explicitly, as The_Fox demonstrates here.
Rob Kennedy
@Ian, call `inherited Create(Teapot)` if you want to execute the parent's constructor.
Marcus Adams
@Rob Kennedy: i would have used `Self`, but `Self` normally refers to an object. It's a very gray area to say that the *"object"* exists during the constructor; `Self` might not be valid. i also wanted to include something (*anything*) so that someone coming along later (e.g. me) doesn't say, "Oh hey, he missed the `inherited` here").
Ian Boyd
I don't think it's gray at all, @Ian. By the time any user-written code in a constructor runs, all the object's memory has been allocated and initialized (by implicit calls to `NewInstance` and `InitInstance`, respectively). The object definitely exists, and `Self` is a reference to it. Besides, your unit tests should ensure that nobody thinks "he missed `inherited` here," at least not for any longer than the couple of minutes it takes to make the change and see the tests fail.
Rob Kennedy