Singleton

My program runs fine in both Linux and Windows environment and should be satisfied. NO!!! It doesn’t mean your program is robust and reliable even your program doesn’t run into any issue. Maybe you just be LUCKY! First of all, variable initialization sometimes are random, for instance, static variables in different files. If there is any change of the file order in your compiling process, it may lead to catastrophic result. Second, different platform has different memory management mechanism and tolerance to errors. For example, a singleton that is destructed may still be accessible, however, the program will crash in windows in the spot. Believe it or not, this is my real experience. All in all, the objective of the article is to guide you through some important guidelines that you should follow to make sure your program is more portable and so theoretically correct in the perspective of C++. In addition, some tough technical challenges will also be sorted out and provided in practical solutions.

1) I have a singleton class, how could I make sure it will be destructed in the last place?

This is a really challenge task and it is explained in various C++ technical articles and books.

Solution a) generic approach
// SingletonBase.h: interface for the SingletonBase class.
//
//////////////////////////////////////////////////////////////////////
 
#if !defined(_SINGLETONBASE_H_)
#define _SINGLETONBASE_H_
 
#include <stdio.h>
#include <iostream>
 
class SingletonBase  
{
public:   
   virtual ~SingletonBase()throw()
   {
      printf("~SingletonBase() dtor\n");      
   }
 
    static SingletonBase * instance()
   {
       return myself_;  
   }
   class Init;            // This declares nested class SingletonBase::Init
protected:
   friend class Init;          
       // So SingletonBase::Init can access SingletonBase::SingletonBase
 
   SingletonBase()throw()
   {
      printf("SingletonBase() ctor\n");
      var = 5;
   }       
    static SingletonBase * myself_;    
};
 
class SingletonBase::Init 
{
public:
  Init()  throw() { 
     if (count_++ == 0) 
        SingletonBase::myself_ = new SingletonBase(); 
  }
  ~Init() throw() { 
     if (--count_ == 0) 
        delete myself_; 
  }
private:
  static unsigned count_;
};
 
static SingletonBase::Init SingletonBaseInit;  
// The key: a static SingletonBase::Init object defined in the header file
 
#endif // !defined(_SINGLETONBASE_H_)
 
// SingletonBase.cpp: implementation of the SingletonBase class.
//////////////////////////////////////////////////////////////////////
 
#include "SingletonBase.h"
 
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
 
SingletonBase* SingletonBase::myself_ = NULL;
unsigned SingletonBase::Init::count_ = 0;

Notes: you can make the above SingletonBase? to be a template.

Pros: - almost guarantee the singleton to be destructed when program exists through some tricks, also a neat implementation; Cons: - not thread safe; - increase program start up time since the static variable has to be initialized no matter the singleton is to be used or not. - If outside variable is also a static variable defined in header file and in front of Singleton, its access to singleton will result crash;

Solution b) lazy-initialization
……
        static SingletonBase * instance()
    {
              If(myself_==null)
                   myself_= new SingletonBase();
 
              return myself_;  
    }
       ……

Pros: - The singleton will only be initialized in the first time of instance() call. - The singleton will always be accessible from outside variables.;

Cons: - not thread safe; - sometimes people worry about the fact that the solution’s "leaks." In many cases, this is not a problem, but it is a problem in some cases. Note: even though the object pointed to by myself_ is never deleted, the memory doesn't actually "leak" when the program exits since the operating system automatically reclaims all the memory in a program's heap when that program exits. In other words, the only time you'd need to worry about this is when the destructor for the Singleton object performs some important action (such as writing something to a file) that must occur sometime while the program is exiting.

Solution c) tackle race condition pitfall

The Problem: Race Conditions Unfortunately, the canonical implementation of the Singleton pattern shown above does not work in the presence of preemptive multi-tasking or true parallelism. For instance, if multiple threads executing on a parallel machine invoke Singleton::instance simultaneously before it is initialized, the Singleton constructor can be called multiple times because multiple threads will execute the new Singleton operation within the critical section shown in the previous solution.

class Singleton
{
public:
    static Singleton *instance (void)
    {
// Constructor of guard acquires
// lock_ automatically.
Guard<Mutex> guard (lock_);
 
// Only one thread in the
// critical section at a time.
if (instance_ == 0)
    instance_ = new Singleton;
return instance_;
 
// Destructor of guard releases
// lock_ automatically.
    }
private:
static Mutex lock_;
static Singleton *instance_;
};

This reduces locking overhead it, but doesn’t provide threadsafe initialization. There is still a race condition in multithreaded applications that can cause multiple initializations of instance . For instance, consider two threads that simultaneously check for myself_ == NULL. Both will succeed, one will acquire the lock via the guard, and the other will block. After the first thread initializes the Singleton and releases the lock , the blocked thread will obtain the lock and erroneously initialize the Singleton for a second time.

There is a Double-Checked Locking solution to fix the thread safe issue.almost identical to the previous one. Unnecessary locking is avoided by wrapping the call to new with another conditional test, as follows:

class Singleton
{
    public:
static Singleton *instance (void)
{
        // First check
    if (instance_ == 0)
    {
       // Ensure serialization (guard
       // constructor acquires lock_).
      Guard<Mutex> guard (lock_);
      // Double check.
      if (instance_ == 0)
      instance_ = new Singleton;
           }
    return instance_;
    // guard destructor releases lock_.
    }
private:
static Mutex lock_;
static Singleton *instance_;
};
Solution d) the good one you should consider

Take advantage of ACE_Singleton, which is a very complicated class and comes with handy solution to take care of all above loopholes. ACE_ACE_Object_Manager will guarantee the release of all registered ACE_Singleton. Example:

typedef ACE_Singleton<CPITraceBase, ACE_Recursive_Thread_Mutex> CPITrace;

Reference: i) http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14 ii) Double-Checked Locking An Optimization Pattern for Efficiently …http://www.cs.wustl.edu/~schmidt/PDF/DC-Locking.pdf iii) Object Lifetime Manager 1 Introduction 2 The Object Lifetime … http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf

2) I just read your part about a singleton class, how could I make it work in DLL that will load and loaded the singleton?

Solution: You need to use ACE_Unmanaged_Singleton . One approach I have used is to have dll_load() and dll_unload () functions and to simply use an ACE_Unmanaged_Singleton. Then, in dll_unload (), you call the static close() function on the singleton type to explicitly clean up the singleton object. Of course, the onus is on the application to ensure it calls close() before unloading the DLL.

3) Could you please explain me Initialization of Static and Global Variabless in C++?

Under certain circumstances, one must be very careful about how one initializes global variables, class static data members, and file (static) variables.

* All global or file(static) data in a C++ program are initialized before access. Most of these static initializations are accomplished when the program image is loaded, before execution begins. If no explicit initializer is provided, the data are initialized to "all zeros":

the initialization order is only followed within a compilation unit. A compilation unit (or unit for short) is a paired source file and its matching header file. Each unit may be compiled separately. you can't predict (in a platform-neutral manner, at least) the initialization order of objects that are declared in separate translation units. Here's an

* Non-static (normal) local variables

Normal local variables will be initialized at the beginning of each function call (before any function code is executed). Note that local variables must not be initialized by the function itself or functions which call this function. Otherwise an endless loop results.

local object starts out containing garbage, unless they are explicitly initialized. Nothing useful can be predicted about the garbage.

* Dynamically-allocated memory obtained with malloc and realloc is also likely to contain garbage, and must be initialized by the calling program, as appropriate. Memory obtained with calloc contains all-bits-0

* Static local variables within function. Static local variables will be initialized only on the first function call. So the execution time of the first function call will be longer than the subsequent calls. Also there is again the potential for creating endless loops as mentioned above for non-static local variables.

* The order in which a class object's components are initialized is fixed to a precise order by the C++ language definition.

*
o Virtual base class subobjects, no matter where they occur in the hierarchy

*
o Nonvirtual immediate base classes, in the order they appear on the base class list

*
o The data members of the class, in the order they are declared

* C++ guarantees that objects are initialized before any other user-defined objects with static storage type. Because objects are always destroyed in the opposite order of their construction, you can safely use the < iostream> objects in the destructors of objects with static storage type as well.

for details, please refer:

http://aips2.nrao.edu/docs/notes/173.text http://www.lysator.liu.se/c/c-faq/c-17.html http://www.beck-ipc.com/files/ipc/documentation/applicationnotes/AN_CPP_V2.pdf

4) Difference between Static and global variable

Excerpt from online : http://c.ittoolbox.com/documents/popular-q-and-a/difference-between-static-global-variable-1596 http://www.scit.wlv.ac.uk/cbook/chap11.static.functions.html

Static and global variable differ a lot in their behavior to life and scope. First, let me distinguish between life and scope. Life of an object determines whether the object is still in the memory (of the process) whereas scope of the object is whether can I know the variable by its name at this position. It is possible that object is live, but not visible (not in scope) but not that object is not alive but in scope (except for dynamically allocated objects where you refer object through pointers).

Static variables are local in scope to their module in which they are defined, but life is throughout the program. Say for a static variable inside a function cannot be called from outside the function (because it's not in scope) but is alive and exists in memory. The next time this function is entered (within the same program) the same chunk of memory would be accessed now retaining the variables old value and no new memory is allocated this time for this variable like other variables in the function (automatic variables). So basically the variable persists throughout the program. Similarly if a static variable is defined in a global space (say at beginning of file) then this variable will be accessible only in this file (file scope).

On the other hand global variables have to be defined globally, persists (life is) throughout the program, scope is also throughout the program. This means such variables can be accessed from any function, any file of the program.

So if you have a global variable and you are distributing your files as a library and you want others to not access your global variable, you may make it static by just prefixing keyword static (of course if same variable is not required in other files of yours).

To access outside global variables or function, you need to use keyword “extern”. However, static and global variables are not recommended in C++ applications.

- excerpt from book c++ gotcha we can code the initialization order explicitly, using standard techniques. One such standard technique is a Schwarz counter, so called because it was devised by Jerry Schwarz and is employed in his implementation of the iostream library:

gotcha55/term.h
 
extern const char *terminalType; 
//other things to initialize . . .
class InitMgr { // Schwarz counter
 public:
   InitMgr()
       { if( !count_++ ) init(); }
   ~InitMgr()
       { if( !--count_ ) cleanup(); }
   void init();
   void cleanup();
 private:
   static long count_; // one per process
};
namespace { InitMgr initMgr; } // one per file inclusion
 
 gotcha55/term.cpp
 
extern const char *terminalType = 0; 
long InitMgr::count_ = 0;
void InitMgr::init() {
   if( !(terminalType = getenv( "TERM" )) )
       terminalType = "VT100";
   // other initializations . . .
}
void InitMgr::cleanup() {
   // any required cleanup . . .
}

A Schwarz counter counts how many times the header file in which it resides is #included. There is a single instance, per process, of the static member count_ of InitMgr?. However, every time the header file term.h is included, a new object of type InitMgr? is allocated, and each of these requires a runtime static initialization. The InitMgr? constructor checks the count_ member to see if this is the "first" initialization of an InitMgr? object of the process. If it is, the initializations are performed.

Conversely, when the process terminates normally, static objects that have destructors will be destroyed. With each InitMgr? object destruction, the InitMgr? destructor decrements the count_. When count_ reaches zero, any required cleanup is performed.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccelng/htm/basic_17.asp

5) Take Charge and Initialize Your Own Data

http://www.devx.com/getHelpOn/Article/16039/0/page/2

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License