Introduction to the singleton pattern in C++

  • 2020-04-01 21:37:04
  • OfStack

There are many places where such functional modules are needed, such as system log output, GUI applications must be single mouse, MODEM connection requires one and only one phone line, operating system can only have a window manager, a PC with a keyboard.

There are many ways to implement the singleton pattern, and in C++ you can even do this directly with a global variable, but the code is not elegant. Using global objects guarantees easy access to instances, but there is no guarantee that only one object can be declared -- that is, local instances of the same class can still be created with one global real exception.

A nice implementation in design patterns is to define a singleton class that USES a private static pointer to a unique instance of the class and a public static method to get that instance.

The singleton pattern manages its unique instance through the class itself, which provides a solution to the problem. The only instance is a normal object of the class, but the class is designed to create only one instance and provide global access to that instance. The unique instance class Singleton hides the action to create an instance in the static member function. This member function is customarily called Instance(), and its return value is a pointer to the unique Instance.
The definition is as follows:


class CSingleton
{
//The other members
public:
static CSingleton* GetInstance()
{
      if ( m_pInstance == NULL )  //Determines if the first call is made
        m_pInstance = new CSingleton();
        return m_pInstance;
}
private:
    CSingleton(){};
    static CSingleton * m_pInstance;
};

The only way a user can access a unique instance is by the GetInstance() member function. Without this function, any attempt to create an instance will fail because the constructor of the class is private. GetInstance() USES lazy initialization, which means that its return value is created when the function is first accessed. This is a bulletproof design -- all calls after GetInstance() return Pointers to the same instance:

CSingleton* p1 = CSingleton :: GetInstance();
CSingleton * p2 = p1 - > The GetInstance ();
CSingleton & ref = * CSingleton :: GetInstance();

With a few modifications to GetInstance, this design template can be used for mutable multi-instance situations, such as up to five instances per class.

The singleton class CSingleton has the following characteristics:

It has a static pointer to a unique instance, m_pInstance, and is private.
It has a public function that takes the unique instance and creates it if needed.
Its constructor is private, so an instance of the class cannot be created elsewhere.

Most of the time, this implementation is fine. Experienced readers may ask, when is the space that m_pInstance points to released? The more serious question is, when is the destructor of the instance executed?
The code above cannot do this if there are necessary operations in the destructive behavior of the class, such as closing the file and freeing external resources. We need a way to delete the instance normally.

You can call GetInstance() at the end of the program and drop the delete operation on the returned pointer. This can be functional, but it is ugly and error prone. Because such additional code is easy to forget, and it's hard to guarantee that no code calls the GetInstance function after delete.
A good way to do this is to let the class know to delete itself at the right time, or to hang the action of deleting itself at the right point in the operating system so that it is automatically executed at the right time.
We know that at the end of the program, the system will automatically destruct all global variables. In fact, the system will also destruct static member variables of all classes, just as these static members are also global variables. Using this feature, we can define such a static member variable in a singleton class whose only job is to remove the instance of the singleton class from the destructor. The CGarbo class in the following code (Garbo means garbage worker) :


class CSingleton
{
//The other members
public:
static CSingleton* GetInstance();
private:
    CSingleton(){};
    static CSingleton * m_pInstance;
class CGarbo //Its only job is to remove the instance of CSingleton from the destructor
{
        public:
            ~CGarbo()
            {
                if( CSingleton::m_pInstance )
                  delete CSingleton::m_pInstance;
}
         }
        Static CGabor Garbo; //Define a static member whose destructor is automatically called when the program ends
} ; 

The CGarbo class is defined as a private inline class for CSingleton in case it is misused elsewhere.
At the end of a program run, the system calls the destructor of CSingleton's static member Garbo, which deletes a unique instance of the singleton.
Using this method to release singletons has the following characteristics:
Define a proprietary nested class within the singleton class.
Defines a private static member within the singleton class that is specifically used for release.
Using the characteristic of destructing global variables at the end of the program, the final release time is selected.
Code that USES singletons does not require any action and does not care about the release of the object.

Further discussion

However, adding a static object of a class is always unsatisfactory, so someone used the following method to reproduce the implementation of the singleton and solve its corresponding problems. The code is as follows:


class CSingleton
{
    //The other members
    public:
        static Singleton &GetInstance()
{
    static Singleton instance;
    return instance;
}
        private:
            Singleton() {};
};

The use of local static variables, a very powerful method, fully implemented singleton features, and less code, and do not worry about singleton destruction.
But there are problems with this approach as well. The problem arises when the following approach USES singletons,

Singleton :: GetInstance();

Doing so presents a class copy problem, which violates the singleton's nature. The problem is that the compiler generates a default constructor for the class to support the copy of the class.
In the end, there was no way out. We would forbid class copying and class assignment, and forbid programmers to use singletons in this way. At that time, the leader meant that the GetInstance() function returned a pointer instead of a reference.


static Singleton *GetInstance()
{
static  Singleton instance;
return  &instance;
}

But I always feel bad, why not let the compiler not do it. Only then did I remember the constructor to display the copy of the life class, and the overloading = operator. The new singleton class is as follows:


class Singleton
{
    //The other members
    public:
        static Singleton &GetInstance()
{
    static Singleton instance;
    return instance;
}
        private:
            Singleton() {};
            Singleton(const Singleton);
            Singleton & operate = (const Singleton&);
};

About the Singleton (const Singleton); And Singleton& operate = (const Singleton&); Functions that need to be declared private and only declared and not implemented. In this way, if you use singletons in the way above, the compiler always reports an error, whether in a friend class or otherwise.
I don't know if this singleton class is still a problem, but it's pretty much fine to use it in a program like this.


Optimize the Singleton class for single-threaded applications
Singleton allocates storage space for a unique instance using the operator new. Because the new operator is thread-safe, you can use this design template in multithreaded applications, but there is a drawback: you must manually destroy the instance with delete before the application terminates. Otherwise, not only will you run out of memory, but you will also cause unpredictable behavior, because Singleton's destructor will not be called at all. Single-threaded applications can easily avoid this problem by using local static instances instead of dynamic ones. Here's a slightly different implementation from GetInstance() above, which is dedicated to single-threaded applications:


CSingleton* CSingleton :: GetInstance()
{
    static CSingleton inst ; 
    return &inst ; 
}

Local static object instance inst is the first call GetInstance () is constructed, always remain active until the application is terminated, pointer m_pInstance become redundant and can be removed from the class definition, and dynamically allocated object, static object when the application is automatically at the termination of the destroyed, so they don't have to manually destroy instance.

Code to learn


//Version of a
#include <iostream>   
using namespace std;   
//C++ implementation of singleton class & NBSP;  
class Singleton   
{   
private:   
       Singleton();//Note: constructor private & NBSP;  

       static Singleton* instance;//Unique example & NBSP;  
       int var;//Member variable (for testing)& NBSP;  
public:   
       static Singleton* GetInstance();//Factory method (to get an instance)& NBSP;  
       int getVar();//Get the value of var & NBSP;  
       void setVar(int);//Set the value of var & PI;  
       virtual ~Singleton();
};   
//Constructor implementation & NBSP;  
Singleton::Singleton()   
{   
       this->var = 20;   
       cout<<"Singleton Constructor"<<endl;   
}   
Singleton::~Singleton()   
{   
       cout<<"Singleton Destructor"<<endl;
       //delete instance;   
}   
//Initializing static members & NBSP;  

Singleton* Singleton::instance=new Singleton;
Singleton* Singleton::GetInstance()   
{   
       return instance;   
}     
//Seter && getter inclusions & NBSP;  
int Singleton::getVar()   
{   
       return this->var;   
}   
void Singleton::setVar(int var)   
{   
       this->var = var;   
}   
//main   
void main()   
{   
       Singleton *ton1 = Singleton::GetInstance();   
       Singleton *ton2 = Singleton::GetInstance();
      if(ton1==ton2)
              cout<<"ton1==ton2"<<endl;
       cout<<"ton1 var = "<<ton1->getVar()<<endl;
       cout<<"ton2 var = "<<ton2->getVar()<<endl;   
       ton1->setVar(150);   
       cout<<"ton1 var = "<<ton1->getVar()<<endl;
       cout<<"ton2 var = "<<ton2->getVar()<<endl;
       delete Singleton::GetInstance();//Must be explicitly deleted
}   


Related articles: