A constrained singleton sanity check

S

stevewilliams2004

I am attempting to create a singleton, and was wondering if someone
could give me a sanity check on the design - does it accomplish my
constraints, and/or am I over complicating things. My design
constraints/environment are as follows:

1) Everything is single-threaded during static initialization (as in
prior to the open brace of main)
2) The environment may be multi-threaded during nominal program
execution (within {} of main)
3) I want a guaranteed destructor call to the singleton (excluding
exceptions, or other abnormal circumstances).
4) Other static (and/or) global objects may access the singleton
during their destructors.

The real kicker I was working to accommodate for above is #4. The
Idea I had was to basically hand out reference-counted containers for
the singleton. The tricky part was giving the guarantee that static/
global objects could reference the singleton in their destructors.
The idea was that even after {} main exits, the reference count is
reduced to zero and the singleton is destroyed, but some object's
destructor could still ressurect the singleton by asking for an
instance (which would presumably be destroyed again shortly after the
local use of that reference was destroyed). I tried to minimize the
code as much as possible, sorry in advance it being 117 lines...

//Header

#include <iostream>
using namespace std;

#ifndef SINGLETON_H_
#define SINGLETON_H_

void _Lock();
void _Unlock();

class Singleton;

class SingletonReference {
friend class Singleton;
public:

virtual ~SingletonReference();
SingletonReference(const SingletonReference &source);
Singleton& Get();

protected:

SingletonReference(Singleton **ppInstance);

private:

Singleton *m_pInstance;
};

class Singleton {
friend class SingletonReference;
public:

static SingletonReference Instance();
virtual ~Singleton() {}
void DoSomething();

protected:

Singleton() {}

private:

static Singleton* AddReference();
static void RemoveReference();
Singleton(const Singleton&) {}

static Singleton* s_pInstance;
static size_t s_count;
static SingletonReference s_instance; //Optional, keep-alive to
avoid lots of destroy/realloc

}; //class Singleton

#endif //SINGLETON_H_

//Implementation

//static
Singleton* Singleton::s_pInstance = NULL;
size_t Singleton::s_count = 0;
SingletonReference s_instance = Singleton::Instance();

void _Lock() {
//Some Implementation defined lock*/
}
void _Unlock() {
//Some Implementation defined unlock*/
}

SingletonReference::~SingletonReference() {
Singleton::RemoveReference();
}

SingletonReference::SingletonReference(const SingletonReference
&source)
: m_pInstance(NULL) {
Singleton::AddReference();
m_pInstance = source.m_pInstance;
}

Singleton& SingletonReference::Get() {
return *m_pInstance;
}

SingletonReference::SingletonReference(Singleton **ppInstance)
: m_pInstance(NULL) {
Singleton::AddReference();
m_pInstance = *ppInstance;
}

SingletonReference Singleton::Instance() {
return SingletonReference(&s_pInstance);
}

void Singleton::DoSomething() { cout << "Hi" << endl; }

Singleton* Singleton::AddReference() {
_Lock();
if (s_pInstance == NULL) {
s_pInstance = new Singleton();
s_count = 1;
}
else
s_count++;
_Unlock();
return s_pInstance;
}

void Singleton::RemoveReference() {
_Lock();
if (--s_count == 0)
{
delete s_pInstance;
s_pInstance = NULL;
}
_Unlock();
}
 
J

James Kanze

I am attempting to create a singleton, and was wondering if
someone could give me a sanity check on the design - does it
accomplish my constraints, and/or am I over complicating
things. My design constraints/environment are as follows:
1) Everything is single-threaded during static initialization (as in
prior to the open brace of main)
2) The environment may be multi-threaded during nominal program
execution (within {} of main)
3) I want a guaranteed destructor call to the singleton (excluding
exceptions, or other abnormal circumstances).
4) Other static (and/or) global objects may access the singleton
during their destructors.

Do you ever actually need both 3 and 4 in the same program?
The real kicker I was working to accommodate for above is #4.

4 is easy. So is 3. Both together become very difficult, if
not impossible.
The Idea I had was to basically hand out reference-counted
containers for the singleton. The tricky part was giving the
guarantee that static/ global objects could reference the
singleton in their destructors. The idea was that even after
{} main exits, the reference count is reduced to zero and the
singleton is destroyed, but some object's destructor could
still ressurect the singleton by asking for an instance (which
would presumably be destroyed again shortly after the local
use of that reference was destroyed).

But a resurected singleton isn't the same instance as the
original singleton. If you resurected, it's not really a
singleton anymore.

Note that your code may cause multiple creations during program
start-up as well. All it guarantees is that there is never more
than one instance of a singleton alive at any given time. Which
is fine if the singleton has no variable state, but if it has no
variable state, one can argue that there's no need for it to be
a singleton in the first place.
 
S

stevewilliams2004

Do you ever actually need both 3 and 4 in the same program?

But a resurected singleton isn't the same instance as the
original singleton. If you resurected, it's not really a
singleton anymore.

Note that your code may cause multiple creations during program
start-up as well. All it guarantees is that there is never more
than one instance of a singleton alive at any given time. Which
is fine if the singleton has no variable state, but if it has no
variable state, one can argue that there's no need for it to be
a singleton in the first place.

Well, the the singleton in this program is actually behaving as a
proxy for a remote object outside the program (and eventually might be
even across a network), which does have a variable state. The purpose
of this singleton is to open the remote connection, provide the remote
interface, and cleanly close the connection when it is done. So in
fact I don't mind a few extra open/close pairs during static/global
init or during static/global cleanup - because this is only for the
brief periods of program startup and shutdown (I don't like it, but
can live with it if I am still satisfying my constraints).
I care more that:
1) No messages to the remote server are lost (at least the code on
this end does everything it can to ensure this), such as another
static/global object that needs to utilize the remote log service
during its destructor to record important diagnostic info.
2) The connection is cleanly closed so there won't be issues trying to
reconnect if the client (this module) restarts suddenly. I can't
touch the server code, and it likes the client to disconnect before
allowing it to connect again.

Maybe I'm approaching the whole problem a bit wrong, it just seemed to
me the singleton was the closest to what I needed but it ended up
needed much more tweaking than I expected.

Thanks,
I do very much appreciate your feedback
 
J

James Kanze

Well, the the singleton in this program is actually behaving as a
proxy for a remote object outside the program (and eventually might be
even across a network), which does have a variable state. The purpose
of this singleton is to open the remote connection, provide the remote
interface, and cleanly close the connection when it is done. So in
fact I don't mind a few extra open/close pairs during static/global
init or during static/global cleanup - because this is only for the
brief periods of program startup and shutdown (I don't like it, but
can live with it if I am still satisfying my constraints).

OK. It's a special case. I wouldn't make it a generic
solution. (99% of the time, I don't need a destructor in my
singletons.) But if it solves a specific problem, why not?

[...]
Maybe I'm approaching the whole problem a bit wrong, it just
seemed to me the singleton was the closest to what I needed
but it ended up needed much more tweaking than I expected.

Well, it's not a singleton in the true sense, but it could be
what you need in this special case.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top