 	
Singletons

Singletons are evil, at least, that's what everyone seems to say. But like anything, they have their place.

Singletons are object classes that guarantee at least two things:

1. One and only one object can be instantiated, and
2. A global access point to that instantiation will be provided.

The problem some see with singletons is that the idiom can be overused; they are, essentially, global objects, 
and many try to avoid global objects like the plague. But sometimes singletons are practical, and logical, even 
necessary. Consider a logger or kernel. It makes sense that only one of these object classes need be instantiated 
at any one time, and for most applications, it would also be necessary to provide a global point of access to them - 
all parts of an application might want to log information or request kernel services at some point.

There are a few ways to control object instantiation. Here's a simple example of the 'static member procedure' 
method, using a bare-bones logger class. First, the code:

Code:

type Logger
public:
	declare static function GetInstance () as Logger ptr
	' ...
private:
	declare constructor ()
	declare constructor (byref as Logger)
	declare destructor ()
	declare operator let (byref as Logger)
	' ...
end type

function Logger.GetInstance () as Logger ptr
	static theinstance as Logger
	return @theinstance
end function


The idea is to create a static member procedure that contains a static singleton object - theinstance - which it 
then returns the address of. Static variables and objects are created when they are first encountered - the first 
time Logger.GetInstance is called - and are destroyed when the program ends. This has the benefit of delaying 
creation of the singleton object until it is needed, and also guaranteeing its existence thereafter.

All constructors - in this case the default and copy constructor - as well as the destructor and assignment operator 
are declared private, to prevent user code from creating, copying or destroying Logger objects; only Logger member 
procedures are allowed to do that. In fact, in this small example, the copy constructor and assignment operator are 
not even defined. That's the basic idea, here is the rest of Logger's TYPE and member procedure definitions:

Code:

type Logger
public:
	' .. as before ..
	
	declare function Open (byref filename as string, byval doAppend as integer = -1) as integer
	declare sub Close ()
	
	declare sub Log (byref text as string)

private:
	' .. as before ..

	const m_invalidFilenum as integer = &Hdeadc0de
	m_filenum as integer = m_invalidFilenum
end type

constructor Logger ()
end constructor

destructor Logger ()
	this.Close()
end destructor

function Logger.Open (byref filename as string, byval doAppend as integer) as integer
	var filenum = ..freefile()
	
	var res = iif (doAppend, _
		..open(filename, for append, as filenum), _
		..open(filename, for output, as filenum))
	
	m_filenum = iif (res, m_invalidFilenum, filenum)
	return res
end function

sub Logger.Close ()
	if (m_filenum <> m_invalidFilenum) then
		..close(m_filenum)
		m_filenum = m_invalidFilenum
	end if
end sub

sub Logger.Log (byref text as string)
	if not (m_filenum = m_invalidFilenum) then
		print #m_filenum, text
	end if
end sub


The default constructor does nothing in this example. Logger.Open opens a file for logging, appending to it by 
default. Logger.Log logs text to the file and Logger.Close closes the file. Here is an example of usage:

Code:

scope
	var thelogger = Logger.GetInstance()
	
	if (thelogger->Open("filename.txt")) then
		print "error: Could not open log file."
		end -1
	end if
	
	thelogger->Log("some text")
	thelogger->Log("some more text")
	
	thelogger->Close()
end scope