Solved

How to prevent Constructor from instantiating an Object

Posted on 1998-08-12
16
409 Views
Last Modified: 2010-04-04
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
0
Comment
Question by:andrewjackson
  • 6
  • 5
  • 2
  • +3
16 Comments
 
LVL 2

Expert Comment

by:333
ID: 1361971
Have you add the override directive to the declaration? Like

constructor Create;override;


0
 

Author Comment

by:andrewjackson
ID: 1361972
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.
0
 
LVL 4

Expert Comment

by:erajoj
ID: 1361973
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
0
 
LVL 3

Expert Comment

by:rickpet
ID: 1361974
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 ...
0
 

Author Comment

by:andrewjackson
ID: 1361975
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.


0
 
LVL 10

Expert Comment

by:Jacco
ID: 1361976
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.
0
 

Author Comment

by:andrewjackson
ID: 1361977
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
0
 
LVL 10

Accepted Solution

by:
Jacco earned 50 total points
ID: 1361978
Here is my try. I think this will answer your question.

First I designed an abstract class to be an ancestor of all "lonely classes" you want to create:

*** start of code ***
unit Unit2;

interface

uses
  Classes, SysUtils;

type
  TLonelyClass = class
  protected
    function PreviousExists : Boolean; virtual; abstract;
  public
    constructor Create; virtual;
  end;

implementation

constructor TLonelyClass.Create;
begin
  if PreviousExists then
    raise Exception.Create('There can be only one');
end;

end.
*** end of code ***

This class has an abstract function PreviousExists that should return if a instance of this class has already been instanciated.

The following class is an example of a derived class from this class.

Note that the constructor is virtual and can be overriden. This is done because each derived class should have its own storage for its instance.

*** start of code ***
unit Unit3;

interface

uses
  Unit2;

type
  TALonelyClass = class(TLonelyClass)
  protected
    function PreviousExists : Boolean; override;
  public
    constructor Create; override;
  end;

var
  ALonelyClass : TALonelyClass = nil;

implementation

constructor TALonelyClass.Create;
begin
  inherited Create;
  ALonelyClass := Self;
end;

function TALonelyClass.PreviousExists : Boolean;
begin
  Result := ALonelyClass <> nil;
end;

initialization
  TALonelyClass.Create;
finalization
  ALonelyClass.Free;
end.
*** end of code ***

The call to inherited Create calls the overriden PreviousExists which check the storage if there has already been an instance created. The constructor also assign this one.

Classes that derive from this class must override PreviousExists to point to another instance storage. And implement a constructor that assigns the new storage with the ..hopefully.. created Self.

Regards Jacco

P.S.

1. To finish it all of: The base class should also have a abstract method function that retrieves its instance:

class function Instance : TLonelyClass; virtual; abstract;

example implementation in TALonelyClass:

function TALonelyClass.Instance : TLonelyClass;
begin
  Result := ALonelyClass;
end;

2. You can also make groups of classes of which only one for all those classes may exist. This is added functionality that comes for free.
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 5

Expert Comment

by:inter
ID: 1361979
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
0
 
LVL 3

Expert Comment

by:rickpet
ID: 1361980
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
0
 
LVL 3

Expert Comment

by:rickpet
ID: 1361981
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.
0
 
LVL 3

Expert Comment

by:rickpet
ID: 1361982
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.

0
 
LVL 3

Expert Comment

by:rickpet
ID: 1361983
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;

0
 

Author Comment

by:andrewjackson
ID: 1361984
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
0
 

Author Comment

by:andrewjackson
ID: 1361985
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.
0
 
LVL 3

Expert Comment

by:rickpet
ID: 1361986
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
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now