Link to home
Start Free TrialLog in
Avatar of andrewjackson
andrewjackson

asked on

How to prevent Constructor from instantiating an Object

I've got a class (TMyObject) that inherits from TObject.

I want the TMyObject.Create method to be intelligent enough to not instantiate an object if certain conditions apply.  I've tried raising exceptions and calling 'abort' from within the constructor which just seems to force the destructor method to be called followed by spurious AV's
elsewhere in my code...  

constructor TMyObject.Create;
begin
   if <some condition> then
     raise Exception()
     { or call 'Abort' }
   else
     inherited Create;
end;

Does anyone know of a clean way to cleanly quit a constructor method and return 'nil' rather than an instance of TMyObject?

Thanks
Avatar of 333
333

Have you add the override directive to the declaration? Like

constructor Create;override;


Avatar of andrewjackson

ASKER

I've not tried but surely its not correct to 'override' a constructor.  A constructor must be a 'class method' rather than an 'object method' since its called before the object is instantiated.  So there can't be a reference to that method in the virtual method table for that object at the point when its called.
Hi,
I haven't tried this very thoroughly but it seems to work, however this is probably not the recommended way to do it:

constructor TMyObject.Create;
begin
  if Condition then
  begin
    Self := nil; //<<<
    Abort;
  end else inherited Create;
end;

Call it like this:
  try
    MyObject := TMyObject.Create;
  except // MyObject = nil
  end;

I tried to instantiate 10000 objects and it didn't consume any stray memory at all.

/// John
Have you tried using a Class Method other than a constructor??

class function TSpecial.CreateSpecial: TSpecial;
begin
  Result := nil;
  if <MyCondition> then
    raise ECreateSpecial //or you could just return nil
  else
    Result := TSpecial.Create;
end;

Rick

P.S. You can override a constructor...how do you think they created...Create(Owner: TComponent);

From the Delphi help

  public
    constructor Create(Owner: TComponent); override;

Like other methods, constructors can be virtual. When invoked through a class-type identifier, as is usually the case, a virtual constructor is equivalent to a static constructor. When combined with class-reference types, however, virtual constructors allow polymorphic construction of objects--that is, construction of objects whose types aren't known at compile time, as described in ...
I do actually use a Class Method (or something like it) but that still relies on other developers remebering to only instaniate TSpecial using that method rather than the constructor.  There is no way (that I know of) of hiding / making private the constructor and thereby preventing it being called so that's why I'm trying to catch the error in the constructor.

FYI
With this code I'm actually trying to implement the 'singleton' design pattern in Delphi.  This is a pattern that permits only one instance of an object that is globally accessible to all other objects in the system.  So the object gets instantated in the 'initialization' section of the unit that it is contained within and destroyed in the 'finalization' section of that unit.  The one instance gets assigned to a global variable.  

The 'condition' in my constructor just checks if that global variable equals nil or not (i.e. whether the only allowable instance has been created or not).  My implementation is based on a couple of examples that I've seen on the web but which both fail because of this constructor problem.

erajoj
I tried your solution but still got AVs.  I wonder is it because I raise an exception / abort during the initialization part of my code.  I presume your tested involved creating 1000 objects AFTER the program had loaded.


If you create one instance of the object in the initialization part of the unit and destroy it in the finalization part.

If your unit is used by any unit in the project. Your object will get instantiated and is globally availably.

If you want to prevent others from instantiating your object put the declaration of your object in the implementation sections. That way opnly the unit the object is declared in can instantiate the object. And you do it once so there is no need for any constaints in the constructor.

The only problem you face then is that other developers can't derive any objects from your class. Since it is not in the interface section.

Regards Jacco

Let me know if this solves your problem or that you want to derive objects from TSpecial in other units.
Deriving other objects isn't so much of an issue but if I declare TSpecial it in the 'implementation' section of a unit then surely no other units will be able to access any of its methods, not just the constructor.  Other units / objects need to be able to access the functionality of the single global instance of TSpecial but not create other instances.

Regards

A
ASKER CERTIFIED SOLUTION
Avatar of Jacco
Jacco
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi,
When an unhandled exception is raised in the constructor Delphi automatically returns nil and reraises the exception. So we should create pseudo exception. Try the following object

// turn optimization off for compiler not to remove our INTENDED exception
{$O-}
constructor TDummyObject.Create(DoIFail: boolean);
begin
  if DoIFail then
   doifail := Boolean(PByte(0)^);//we can not read address 0 so exception
end;
{$O-}

destructor TDummyObject.Destroy;
begin
  inherited;
end;

//this is test routine
procedure TForm1.Button4Click(Sender: TObject);
var
  T : TDummyObject;
begin
  //create and object without failinf
  T := TDummyObject.Create(false);
  ShowMessage(IntToStr(Integer(T)));
  T.Free; T := nil;
  try
  //create an object that fails
  T := TDummyObject.Create(true);
  //if not surrounded with try ... except we see the exception dialog so...
  except
  end;
  ShowMessage(IntToStr(Integer(T)));//you should see 0 in the box
end;

Regards,
Igor
Actually Igor...it's not the constructor that is the problem...

You can raise an exception in the constructor with no problem...the problem is where you raise an exception...You can not let an exception slip past an initialization...If there is a posibility that you will raise an exceiption in the initialization you should handle it and swallow it...

Rick
unit rpSingleton;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  EInstance = class(Exception)
  end;
  TSingleton = class(TObject)
  private
    { Private declarations }
  protected
    { Protected declarations }


  public
    { Public declarations }
//    class function Instance: TSingleton;
    class function Instance: TSingleton;
    constructor create;
  published
    { Published declarations }
  end;


implementation
var
  FSingleton: TSingleton;//no one can see this...except this unit...same as private

constructor TSingleton.Create;
begin
  if Assigned(FSingleton) then
    raise EInstance.Create('Only one instace can be create')
  else begin
    inherited Create;
    FSingleton := Self;
  end;
end;

class function TSingleton.Instance: TSingleton;
begin
  if Assigned(FSingleton) then
    Result := FSingleton
  else begin
    FSingleton := TSingleton.Create;
    Result := FSingleton;
  end;
end;
end.
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, rpSingleton;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }

  end;

var
  Form1: TForm1;
  Singleton: TSingleton;
  MySingleton : TSingleton;
implementation

{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);

begin
  if MySingleton = Singleton then
    Showmessage('same singleton')
  else
    ShowMessage('different singleton');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MySingleton := TSingleton.create;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  MySingleton := Tsingleton.instance;
end;

initialization
  try
  Singleton := TSingleton.create;
  MySingleton := TSingleton.create;
  except
    //we swallow the exception raised by MySingleton...
    //otherwise runtime error 217...GPF
  end;
end.

I think this is the safest method...

  EInstance = class(Exception)
  end;
  TSingleton = class(TObject)
  protected
    constructor init;
  public
    class function Instance: TSingleton;
    constructor create;
  end;

implementation
var
  FSingleton: TSingleton;(*no one can see this...except this unit...same as private*)

constructor TSingleton.Create;
begin
  raise EInstance.Create('Use the Instance method');
end;

constructor TSingleton.Init;
begin
  inherited create;
end;

class function TSingleton.Instance: TSingleton;
begin
  if Assigned(FSingleton) then
    Result := FSingleton
  else begin
    FSingleton := TSingleton.Init;
    Result := FSingleton;
  end;
end;

Thanks everybody for all your suggestions so far. I'll reply again when I've had a chance to test them out.

Thanks again

Andrew
Its taken a while but I think I've finally cracked this one based on ideas that have been posted here and other ideas I've picked up from newsgroups.  The best solution I've seen so far is this one:

unit Singleton;

interface

uses SysUitls, Classes;

type
  TSingleton = class(TObject)
    protected
      // Attributes.
      FRefCount : integer;
      // Methods.
      constructor CreateNew;
      procedure IncRefCount;
      procedure DecRefCount;
    public
      class function Create: TSingleton;
      prodedure Destroy;
    end;

implementation

const
  uSingleton: TSingleton = nil;

// TSingleton Protected Methods.

constructor TSingleton.CreateNew;
begin
  inherited Create;
end;

procedure TSingleton.IncRefCount;
begin
  Inc(FRefCount);
end;

procedure TSingleton.DecRefCount;
begin
  Dec(FRefCount);
end;

// TSingleton Public Methods.

class function TSingleton.Create: TSingleton;
begin
  if uSingleton = nil then
    uSingleton := TSingleton.CreateNew;
  uSingleton.IncRefCount;
  result := uSingleton;
end;

prodedure TSingleton.Destroy;
begin
  DecRefCount;
  if FRefCount = 0 then begin
    inherited Destory;
    uSingleton := nil;
  end;
end;

end.

The beauty of this design is that it hides the real constructor from other classes and doesn't rely on exceptions which seemed to be the source of a lot of problems.  Thanks again for all your help.
I like what you have Andrew...

The only thing I would add would be...

Destructor TSingleton.DestroyInstance;
begin
  inherited Destroy;
  uSingleton := nil;
end;

function Instance: TSingleton;
begin
  result := uSingleton;
end;

The DestroyInstance would allow the programmer to make sure the Singleton was destroyed when he closed the program.

The Instance would allow the program to return nil if the Singleton has not been created or destroyed.

I like the class function of Create...didn't think of it...very cute...

Rick