Board index » delphi » Singleton pattern reveals hole in Delphi privacy

Singleton pattern reveals hole in Delphi privacy

The Singleton pattern is useful when only a single object
of a
class is to be instantiated.  (The code for a Singleton
follows
below; quit reading when you get bored)

In C++, the class manages the instance, in Delphi it's the
'unit'
that gives the control.  When you need to access a member
function, you're supposed to do it like this:

 Writeln(TheSingletonInstance.Member);

and somewhere in your exit code call

 DestroyTheSingleton;

to prevent memory leaks.

This all works.  The 'problem' is that an attempt to make
the
constructor 'private' doesn't work!  It's still possible to
attempt to make another instance, a la

 var aS: TSingleton;
 aS := TSingleton.Create;

which compiles, and sort of runs... aS is, of course, junk.

Bottom line: it is not possible to make the constructor
private.
===========================================================
=====

Code follows...

unit Singleto;

INTERFACE

type
        TSingleton      = class
                destructor Destroy;     override;
                function  Member : string; override;
        private
                constructor Create;     {private; you can't
make it, only I can do that}
        end;

function  TheSingletonInstance: TSingleton; {returns the
instance}
procedure DestroyTheSingleton;

IMPLEMENTATION

const
        aSingleton: TSingleton = nil;

function TheSingletonInstance: TSingleton;
begin
        if aSingleton = nil then begin
                aSingleton := TSingleton.Create;
        end;
        Result := aSingleton;
end;

procedure DestroyTheSingleton;
begin
        aSingleton.Free;
end;

{  -----  TSingleton Methods  -----  }

constructor     TSingleton.Create;
begin                                           { Create }
        inherited Create;
end;                                            { Create }

destructor      TSingleton.Destroy ;    {override}
begin                                           { Destroy }
        inherited Destroy;
end;                                            { Destroy }

function        TSingleton.Member : string;
begin                                           { Member }
        {do something?}
        Result := 'This is the only one you get!';
end;                                            { Member }

end.                                            { Singleton }

--
Grace + Peace
Peter N Roth
Engineering Objects International
Software & Training in C++ & Delphi + Xlations and ports of
Fortran 77

 

Re:Singleton pattern reveals hole in Delphi privacy


In article <4m786f$...@news6.erols.com>,
   Peter N Roth <peter...@mail.erols.com> wrote:
]-The Singleton pattern is useful when only a single object of a
]-class is to be instantiated.  (The code for a Singleton follows
]-below; quit reading when you get bored)
]-
]-In C++, the class manages the instance, in Delphi it's the
]-'unit' that gives the control.  When you need to access a member
]-function, you're supposed to do it like this:
]-
]-  Writeln(TheSingletonInstance.Member);
]-
]-and somewhere in your exit code call
]-
]-  DestroyTheSingleton;
]-
]-to prevent memory leaks.
]-
]-This all works.  The 'problem' is that an attempt to make the
]-constructor 'private' doesn't work!  It's still possible to
]-attempt to make another instance, a la
]-
]-  var aS: TSingleton;
]-  aS := TSingleton.Create;
]-
]-which compiles, and sort of runs... aS is, of course, junk.
]-
]-Bottom line: it is not possible to make the constructor private.

I fooled around a bit with your code...I added a writeln statement to your
constructor to notify me whenever it was called...on the first access of
TheSingletonInstance.Member I get the notification, as expected...however,
the call to
                aS := TSingleton.Create;
does not trigger the notification...looks like TObject.Create is being
called instead.

Apparently it's legal (but very confusing) to declare a private method with
the same name as a public method in an ancestor object.

I think there is a way to make your constructor private, but it's pretty
ugly.  Declare a private constructor with some name other than Create, and
use that to build your singleton instance.  The real trick is to somehow
nullify the inherited Create constructor so that calls to TSingleton.Create
don't create junk instances.  The quick and dirty solution is to have it
cause a runtime error.  Not very nice maybe, but it certainly enforces the
"singularity" of your singleton!

I've included a modified version of your singleton example below...

Mark Vaughan

]-================================================================
]-
]-Code follows...
]-
]-
]-unit Singleto;
]-
]-INTERFACE
]-
]-type
]-  TSingleton  = class
]-    Constructor Create;   {= REPLACES the inherited (from TObject) =}
]-                          {= static constructor                    =}
]-    destructor Destroy; override;
]-    function  Member : string;
]-  private
]-    constructor DoIt; {private; you can't make it, only I can do that}
]-  end;
]-
]-function  TheSingletonInstance: TSingleton; {returns the instance}
]-procedure DestroyTheSingleton;
]-
]-IMPLEMENTATION
]-
]-const
]-  aSingleton: TSingleton = nil;
]-

function TheSingletonInstance: TSingleton;
begin
  if aSingleton = nil then begin
  {=  replace "Create" with "DoIt"  =}
  {=aSingleton := TSingleton.Create;=}
    aSingleton := TSingleton.DoIt;
  end;
  Result := aSingleton;
end;

procedure DestroyTheSingleton;
begin
  aSingleton.Free;
{= added this next line... =}
  aSingleton := NIL;
end;

]-{  -----  TSingleton Methods  -----  }
]-

constructor TSingleton.Create;
begin           { Create }
{= we're gonna invalidate "Create" as a way of constructing   =}
{= a singleton object.  instead of creating an object, a      =}
{= call to Create will now cause a runtime error.  we're also =}
{= gonna lie about what's happening, and claim that "Create"  =}
{= is an abstract virtual method (runtime error 210)          =}
{=inherited Create;=}
  RunError(210);
end;            { Create }

constructor TSingleton.DoIt;
begin           { DoIt }
  inherited Create;
end;            { DoIt }

]-
]-destructor  TSingleton.Destroy ;  {override}
]-begin           { Destroy }
]-  inherited Destroy;
]-end;            { Destroy }
]-
]-function  TSingleton.Member : string;
]-begin           { Member }
]-  {do something?}
]-  Result := 'This is the only one you get!';
]-end;            { Member }
]-
]-end.            { Singleton }
]-
]-
]---
]-Grace + Peace
]-Peter N Roth
]-Engineering Objects International
]-Software & Training in C++ & Delphi + Xlations and ports of
]-Fortran 77

Re:Singleton pattern reveals hole in Delphi privacy


Quote
>type
>    TSingleton      = class
>            destructor Destroy;     override;
>            function  Member : string; override;
>    private
>            constructor Create;     {private; you can't
>make it, only I can do that}
>    end;

        Where's your OVERRIDE?

_
******************************************************************
NOTE: This software is currently in early alpha. If you notice any
problems, or RFC non-compliance, please report it to p...@pobox.com
-Please do not report duplicates, as this is usually a manual resend
-Still working on the references problem......It WILL get fixed
+------------------------------------------------------------+
|Chad Z. Hower  -  phoe...@pobox.com                         |
|Phoenix Business Enterprises - p...@pobox.com - www.pbe.com  |
|Physically in Church Hill, TN - Logically Not Sure          |
+------------------------------------------------------------+

Quote
>>SQUID - The ultimate 95/NT offline databasing reader

**Special Compile: 3.000A (Alpha)

Re:Singleton pattern reveals hole in Delphi privacy


Peter N Roth <peter...@mail.erols.com> wrote:

Quote
>The Singleton pattern is useful when only a single object
>of a
>class is to be instantiated.

[...]

Quote
>Bottom line: it is not possible to make the constructor
>private.
>===========================================================

You can override the NewInstance class method, and then refuse to process
requests for instantiation when a) they do not come from your own class
(to prevent subclassing) or b) when an instance already exists (to enforce
class-level singletons). Note though, that it is still possible to override
NewInstance, but this is pretty rare -- and consider that a person would
have to read the original definition before subclassing, so this is a
management issue, not a technical one.

You can develop all kinds of variations if you want, such as classes that
have instance ID generators, automatic instance templating, instantiation
limits, instantiation monitoring, hooking, etc. etc. Go wild. :-)

(Note: Thanks to Ray Lischner for the tip on this technique.)

Brad Aisa
Business Machine Interfaces, Inc.
Toronto, Canada

-----------------------------------------------

unit MiscClass;

interface

uses
  SysUtils;
type

{ TSingleton -- an object which can only have one instance at a time }

TSingleton = class(TObject)
  private
    class function NewInstance: TObject; override;
  protected
    procedure FreeInstance; override;
    class function InstanceCount(Mode: Integer): Integer; {0=Get, 1=inc,
        -1=dec}
  end;

{ TBarren -- a class which cannot have children (well, they can be defined,
                but never instantiated...}

TBarren = class(TObject)
  private
    class function NewInstance: TObject; override;
  end;

implementation

{ TSingleton }

class function TSingleton.NewInstance: TObject;
begin
  if (InstanceCount(0) = 0) then begin
    InstanceCount(1);
    Result := inherited NewInstance;
    end
  else
    raise Exception.Create(
      'Singleton class ''' + ClassName + ''' already instantiated');
end;

procedure TSingleton.FreeInstance;
begin
  InstanceCount(-1);
  inherited FreeInstance;
end;

{$J+} {for Delphi 2, writeable constants on}

class function TSingleton.InstanceCount(Mode: Integer): Integer;
const
  FInstanceCount: Integer = 0;
begin
  if Mode > 0 then Inc(FInstanceCount)
  else if Mode < 0 then Dec(FInstanceCount);
  Result := FInstanceCount;
end;

{ TBarren }

class function TBarren.NewInstance: TObject;
begin
  if (Self = TBarren) then
    Result := inherited NewInstance
  else
    raise Exception.Create('Cannot subclass ' + ClassName);
end;

end.

Re:Singleton pattern reveals hole in Delphi privacy


Peter N Roth <peter...@mail.erols.com> wrote:

Quote
> Writeln(TheSingletonInstance.Member);
>and somewhere in your exit code call
> DestroyTheSingleton;
>to prevent memory leaks.
>This all works.  The 'problem' is that an attempt to make
>the
>constructor 'private' doesn't work!  It's still possible to
>attempt to make another instance, a la
> var aS: TSingleton;
> aS := TSingleton.Create;

I am sure I read somewhere in the documentation it said
you couldn't increase the privacy of an inherited method or
property. So the only chance is to override the create method - or
maybe don't inherit it from TObject.

C.L.Burke

Re:Singleton pattern reveals hole in Delphi privacy


Quote
CBu...@zoology.uq.edu.au (Christopher Burke) wrote:
>I am sure I read somewhere in the documentation it said
>you couldn't increase the privacy of an inherited method or
>property.

I don't think it would make sense to do that.  You can always refer to
an object as a parent type, and if the method was visible for the
parent, the compiler has no way of knowing that at run-time the object
will really be a descendant type, so it has to make the method
visible.

Quote
>So the only chance is to override the create method - or
>maybe don't inherit it from TObject.

You always inherit Create from TObject; in Delphi, TObject is the
parent of every class, whether you say so or not.  

Duncan Murdoch

Re:Singleton pattern reveals hole in Delphi privacy


p...@pobox.com (Phoenix Business Enterprises) wrote:

Quote
>>type
>>        TSingleton      = class
>>                destructor Destroy;     override;
>>                function  Member : string; override;
>>        private
>>                constructor Create;     {private; you can't
>>make it, only I can do that}
>>        end;

>    Where's your OVERRIDE?

'override' is only required for virtual functions.

ctors themselves are never virtual, but the effect
can be simulated.
--
Grace + Peace
Peter N Roth
Engineering Objects International
Software & Training in C++ & Delphi + Xlations and ports of
Fortran 77

Re:Singleton pattern reveals hole in Delphi privacy


Peter N Roth <peter...@mail.erols.com> wrote:

Quote
>>        Where's your OVERRIDE?

>'override' is only required for virtual functions.

>ctors themselves are never virtual, but the effect
>can be simulated.

In Delphi, constructors can be virtual.  For example, all descendants
of TComponent have virtual constructors.  This lets you create a
component at run time without knowing in advance what type will be
created.

Duncan Murdoch

Other Threads