Singleton mode of design mode (C++)

one sentence description

Make sure a class has only one instance, instantiates itself and provides this instance to the whole system.

understand

  1. "Ensure that a class has only one instance"

    • This requires that its construction method cannot be public, and it must not be called casually by the outside world, that is, it cannot be instantiated by the outside world, so its construction method can only be private .
  2. "and instantiate itself and provide this instance to the whole system"

    • In the case of only one instance, if this instance is provided to the entire system, then this instance must belong to this class, so this instance is a class member variable , that is, it must be a static variable .

    • Moreover, to provide this instance to the entire system, only one

      static member method

      In C++, static variables can only be accessed through static member methods.

effect

Obviously, ensuring that only one instance of a class exists, such as serial number generation , counters , and global management classes , is widely used in software architecture.

two implementations

According to the instantiation period of the singleton class object, the implementation of the singleton class can be divided into two categories.

Hungry Chinese style

#include <iostream>

class Only {
    
    
public:
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* m_singleton;
}
// 静态成员变量(属性)类外初始化,注意需要在cpp文件初始化
Only* Only::m_singleton = new Only();

Obviously, the implementation of the hungry Chinese style is completed when the program is loaded, which is more anxious , so it is called the hungry Chinese style.

Lazy

#include <iostream>

class Only {
    
    
public:
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        if(NULL == m_singleton) {
    
    
            m_singleton = new Only();
        }
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* m_singleton;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;

Obviously, lazy-style instance initialization is lazy when the class instance interface is obtained by calling for the first time. If it is not called, it will not be initialized.

double check lock

From the implementation principle of the lazy man , it can be expected that in the case of multi-threading, a method may be static Only* GetInstance()called by multiple threads at the same time, so it will static Only* singletonbe initialized multiple times, resulting in a memory leak. What should I do? Woolen cloth? lock .

#include <iostream>
#include <mutex>

// 锁
mutex m_Only_mutex;

class Only {
    
    
public:
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        m_Only_mutex.lock();
        if(NULL == m_singleton) {
    
    
            m_singleton = new Only();
        }
        m_Only_mutex.unlock();
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* m_singleton;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;

It seems that this is enough, but there are actually two places that can be optimized:

  1. The scope of the lock can be further narrowed. After all static Only* GetInstance(), this interface may be called multiple times, so the null statement should be locked internally, then multiple threads may run to the null statement at the same time, so in the first sentence of the mutex area, it is necessary to A null check is performed once, which is a double-checked lock .

  2. When using multi-threading, the current singleton class instance object should be modified with volatile, why? because

    m_singleton = new Only();
    

    Can be split into 3 steps:

    1. Allocate memory
    2. initialize object
    3. will m_singletonpoint to the memory just allocated

This operation process is actually a non-atomic operation, that is to say, during this process, the CPU may be reordered. For example: thread A has executed step 1 and step 3, but has not yet executed step 2. At this time, it is m_singletonnot empty. If thread B calls before thread B, static Only* GetInstance()then B will get an uninitialized but valuable one m_singleton, so it should be used volatile modification to avoid reordering.

#include <iostream>
#include <mutex>

// 锁
mutex m_Only_mutex;

class Only {
    
    
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        // 双重检查锁
        if(NULL == m_singleton) {
    
    
            m_Only_mutex.lock();
            if(NULL == m_singleton) {
    
    
                m_singleton = new Only();
            }
        }
        m_Only_mutex.unlock();
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* volatile m_singleton;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;

Memory recovery optimization

For instances of singleton classes, sometimes the current instance must be released, such as closing files and releasing external resources. But since the instance is a static member variable , this requirement cannot be fulfilled in the destructive behavior of the class. We need a way to gracefully delete the instance.

  1. can be called at the end of the program

    GetInstance()
    

    And call the delete operation on the returned pointer.

    Doing this is functional, not very elegant, and error-prone. Because such additional code is easy to be forgotten, and it is difficult to guarantee that no code will be called again after deleteGetInstance() (for hungry Chinese style).

  2. Let this class know to delete itself when appropriate. In other words, hang the operation of deleting yourself at an appropriate point in the system, so that it will be automatically executed at the appropriate time.

    At the end of the program, the system will automatically destroy all global variables. In fact, the system will also destroy all static member variables of the class, just as these static members are also global variables. Therefore, take advantage of this feature to implement singleton class instances and automatically release resources at the end of the program.

#include <iostream>
#include <mutex>

// 锁
mutex m_Only_mutex;

class Only {
    
    
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        // 双重检查锁
        if(NULL == m_singleton) {
    
    
            m_Only_mutex.lock();
            if(NULL == m_singleton) {
    
    
                m_singleton = new Only();
            }
        }
        m_Only_mutex.unlock();
        return m_singleton;
    }
    
private:
    class AutoRelease {
    
    
	public:
		AutoRelease() {
    
    }
        // 通过AutoRelease类的析构函数,释放m_singleton
		~AutoRelease() {
    
    
			if(m_singleton) {
    
    
				delete m_singleton;
			}
		}
	};

    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* volatile m_singleton;
    // 类静态变量,用于程序退出时,单例类自动析构
    static AutoRelease _autoRelease;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;
Only::AutoRelease Only::_autoRelease;

reference list

  • https://www.bilibili.com/video/BV1af4y1y7sS?spm_id_from=333.999.0.0
  • https://blog.csdn.net/qq_29542611/article/details/79301595
  • https://blog.csdn.net/weixin_44363885/article/details/92838607
  • https://bbs.csdn.net/topics/600461189
  • Singleton mode memory release

Guess you like

Origin blog.csdn.net/suren_jun/article/details/128260164