Gamma's Singleton pattern

  • Thread starter Alexander Dong Back Kim
  • Start date
A

Alexander Dong Back Kim

Hi all,

(The following code is from
http://www.devarticles.com/c/a/Cplusplus/C-plus-plus-In-Theory-The-Singleton-Pattern-Part-I/3/)

class Log {

public:
static Log* Instance() {
if (!m_pInstance)
m_pInstance = new Log;
return m_pInstance;
}
void Write(char const *logline);
bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden

static Log* m_pInstance;
static std::list<std::string> m_data;
};

// in log.cpp we have to add
Log* Log::m_pInstance = NULL;


This is a typical Gamma's singleton pattern. Although I know this
works fine but still don't know why I must add the last line in the
CPP file instead of initializing it in the header. What's the reason
of doing this???

Regards,
Alex Kim
 
A

Alf P. Steinbach

* Alexander Dong Back Kim:
(The following code is from
http://www.devarticles.com/c/a/Cplusplus/C-plus-plus-In-Theory-The-Singleton-Pattern-Part-I/3/)

class Log {

public:
static Log* Instance() {
if (!m_pInstance)
m_pInstance = new Log;
return m_pInstance;
}
void Write(char const *logline);
bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden

static Log* m_pInstance;
static std::list<std::string> m_data;
};

// in log.cpp we have to add
Log* Log::m_pInstance = NULL;


This is a typical Gamma's singleton pattern. Although I know this
works fine but still don't know why I must add the last line in the
CPP file instead of initializing it in the header. What's the reason
of doing this???

You can achieve the same effect with header-only code, but you can't use the
code above as pure header-only code.

The reason is that the definition of m_pInstance then can occur in more than one
compilation unit. And C++ forbids having more than one definition for an
ordinary external linkage variable or and routine, which is called the "ODR",
short for One Definition Rule. Mostly the ODR helps you catch errors where the
same name is inadvertently used for different purposes in different compilation
units, but unfortunately the C++ rules leave it to the implementation whether
any ODR breach will be detected.

There are two main ways of defining the singleton with all header file code.

They use different ways to tell the linker that multiple definitions of the
variable are OK, that they're the same, and that the linker should just pick one.

The first and most common is known as Meyer's singleton, and simply makes the
variable a local static variable:

struct Log
{
static Log& instance()
{
static Log theInstance;
return theInstance;
}
};

This Meyer's singleton will however be destroyed before static variables that
have been initialized before it, so those static variables can't use the logger.
Which presumably is the reason why your example code uses a pointer.

Using a pointer is however no problem with a Meyer's singleton:

struct Log
{
static Log& instance()
{
static Log* theInstance = new Log;
return *theInstance;
}
};

This dynamically allocated Meyer's singleton, however, has the opposite problem,
namely that the singleton will never be destroyed.

In order for outside code to be able to influence the time of destruction (or
whether that should occur at all) you need a pointer that's not just a local
static variable, a pointer with external linkage. Which presumably is the reason
why your example is not a Meyer's singleton. For all header code it can go like
this:

struct Log;

namespace detail {
template< typename Dummy >
struct LogStatics_ { static Log* theInstance; }

template< typename Dummy >
Log* LogStatics_<Dummy> theInstance;

typedef LogStatics_<void> LogStatics;
} // namespace detail

class Log: private detail::LogStatics
{
public:
static Log& instance()
{
if( theInstance == 0 ) { theInstance = new Log; }
return *theInstance;
}

// Functionality to prolong or shorten lifetime or whatever.
};

The reason that this doesn't run afoul of the ODR is that the ODR has a special
exemption for templates, since templates in practice have to be defined in headers.


Cheers & hth.,

- Alf
 
S

Stuart Redmann

Hi all,

    (The following code is fromhttp://www.devarticles.com/c/a/Cplusplus/C-plus-plus-In-Theory-The-Si...)
[snip]

    // in log.cpp we have to add
    Log* Log::m_pInstance = NULL;

This is a typical Gamma's singleton pattern. Although I know this
works fine but still don't know why I must add the last line in the
CPP file instead of initializing it in the header. What's the reason
of doing this???

If you leave it in the header, each compilation unit (.cpp file) will
define a variable called Log::m_pInstance. When the linker tries to
link all translated compilation units (the .obj files), it will see a
symbol called Log::m_pInstance in each file, which will result in a
linker error: multiple definition of symbol Log::m_pInstance.

If you really want to avoid putting things into a .cpp file, you can
use a static object inside the Instance function:

#include <iostream>

class Log
{
public:
static Log* Instance()
{
static Log Instance;
return &Instance;
}
void Write(char const *logline)
{
std::cout << "Logging: " << logline;
}
private:
Log ()
{}
Log(Log const&)
{}
};

int main(int argc, char* argv[])
{
Log::Instance ()->Write ("Hello Log!");
}

The static object has the advantage that it won't leak memory. The
disadvantage is that initialization from multiple threads may lead to
race conditions (see http://stackoverflow.com/questions/246564/what-is-the-lifetime-of-a-static-variable-in-a-c-function)
and also the premature destruction of the variable. To avoid this, you
could stick to the (leaking) pointer:

static Log* Instance()
{
// Now the life-time of the new'ed Log will be longer
// than any other static variable.
static Log* pInstance = NULL;
if (!pInstance)
pInstance = new Log ();
return pInstance;
}


Regards,
Stuart
 
J

Joshua Maurice

There are two main ways of defining the singleton with all header file code.

They use different ways to tell the linker that multiple definitions of the
variable are OK, that they're the same, and that the linker should just pick one.

The first and most common is known as Meyer's singleton, and simply makes the
variable a local static variable:

     struct Log
     {
         static Log& instance()
         {
             static Log theInstance;
             return theInstance;
         }
     };

This Meyer's singleton will however be destroyed before static variables that
have been initialized before it, so those static variables can't use the logger.
Which presumably is the reason why your example code uses a pointer.

Using a pointer is however no problem with a Meyer's singleton:

     struct Log
     {
         static Log& instance()
         {
             static Log* theInstance = new Log;
             return *theInstance;
         }
     };

This dynamically allocated Meyer's singleton, however, has the opposite problem,
namely that the singleton will never be destroyed.

In order for outside code to be able to influence the time of destruction (or
whether that should occur at all) you need a pointer that's not just a local
static variable, a pointer with external linkage. Which presumably is the reason
why your example is not a Meyer's singleton.

The FAQ offers a slightly different take.
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14
As long as X calls Log::instance in X's constructor, then Log's
constructor will exit first, it will start its lifetime first, then X
will be constructed. LIFO rules for static object lifetimes, so as the
Log object finished construction first, it will be destroyed last.

The suggested course of action is just to leak the Log object unless
there is a compelling reason to destroy it, as ensuring all static
objects which use Log call Log::instance in their constructors is
tedious and error prone.
 
A

Alf P. Steinbach

* Joshua Maurice:
Errata: Instead of "need" I should have written "can use". An alternative is a
Meyer's singleton using a (additional) Meyer's singleton for access to the
pointer variable. Which however is a bit unnatural, at least to me.

Which presumably is the reason

The FAQ offers a slightly different take.
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14

It's just presented in opposite order, and not addressing the issue of
all-header code. ;-)

As long as X calls Log::instance in X's constructor, then Log's
constructor will exit first, it will start its lifetime first, then X
will be constructed. LIFO rules for static object lifetimes, so as the
Log object finished construction first, it will be destroyed last.

Yes. In that scenario the X instance hasn't been (fully) initialized before the
singleton, and so the singleton instance isn't destroyed before the X instance.
However, as I wrote, the singleton will be destroyed before static variables
that /have/ been initialized before it, e.g. a Y instance that accesses the
singleton only in the Y destructor, and I guess that's what you refer to below:

The suggested course of action is just to leak the Log object unless
there is a compelling reason to destroy it, as ensuring all static
objects which use Log call Log::instance in their constructors is
tedious and error prone.

A sort of obvious solution is to make the 'instance' member protected, which of
course means a less straightforward usage from free-standing routines, but I
don't think that's a problem.

Also, more general singleton lifetime management is not so very tedious and
error prone when you can reuse others' work ;-).

For example, "Modern C++ Design" has a good deal of support for singleton
lifetime management, and I guess that's part of Loki.


Cheers & hth.,

- Alf
 

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

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top