Several Realizations of C++ Singleton Pattern

The concept of Singleton Pattern

schema definition

Ensures that there is only one instance of a class and provides a global access point to it.

Hungry Singleton and Lazy Singleton

The common singleton pattern has two branches, 饿汉单例and 懒汉单例.

  • Hungry singleton means that the singleton object is created when the program is initialized. Its advantage is that it can be obtained directly when the object is to be used. The disadvantage is that no matter whether the object is called or not, it will be created to occupy memory.
  • The lazy singleton means that the corresponding object is created when the singleton object is called for the first time. Its advantage is that objects that are not called will not be created. The disadvantage is that it will take more time to create an object, resulting in the particularity of the first call, and the object is not thread-safe when it is created.

Recommendations:
A singleton should be used, and the initialization of the singleton object should be executed directly at the beginning of the main thread .
This has many advantages. First, it guarantees that no objects will be generated when the object is created 竞争条件(race condition), making the code writing simple and clear (if it is not created in the main thread, multiple threads should consider creating objects at the same time. At this time, it needs to be restricted by means such as mutexes. Although the static keyword of C++11 can ensure that only one object is created, it is still not recommended to use it like this);

Implementation of the singleton pattern

1. Implementation of the most basic hungry man singleton mode

#include <iostream>

class SingletonClass {
    
    
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
			return m_pInstance;
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //单例对象的声明
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
    
    
	//在主线程最开始的地方就创建出单例对象,相当于饿汉单例
	SingletonClass::getInstance();

	//...

	//多次调用,获取的都是同一个对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	//...

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
SingletonClass的单例对象被创建
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
对象的地址为 00000172D3499600
SingletonClass的单例对象被销毁

2. Provide an additional interface, so that the Hungry Singleton can be reflected in the interface design

In 1, although what we want to achieve is the Hungry Singleton, because no specific interface is provided, the programmer needs to ensure that it is called once in the main function to ensure logical correctness, which is obviously inappropriate. Therefore, an interface can be added to specifically create the Hungry Singleton object. An exception will be thrown when trying to obtain the Hungry Singleton object before the singleton object is created, so as to remind the programmer that this is the Hungry Singleton and should be created in the main function SingletonClass::getInstance();.

#include <iostream>
#include <exception>

class SingletonClass {
    
    
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象
	static void constructInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //单例对象的声明
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
    
    
	//若没有创建单例对象,就尝试获取它,会抛出异常
	try {
    
    
		SingletonClass* obj1 = SingletonClass::getInstance();
	}
	catch (const std::exception& e) {
    
    
		std::cout << e.what() << std::endl;
	}

	//创建单例对象
	SingletonClass::constructInstance();

	//...

	//多次调用,获取的都是同一个对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	//...

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
this instance is not construct, please construct the instance in main() function first
SingletonClass的单例对象被创建
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
对象的地址为 000001F768EE9600
SingletonClass的单例对象被销毁

3. Use the RAII mechanism to realize the automatic release of singleton objects

In 1 and 2, the programmer needs to manually recycle the singleton object. In fact, under certain circumstances, the RAII mechanism can be used to automatically release the singleton object. Because the singleton object is actually a static member of the class, special techniques need to be used to manage the life of the singleton object through the life cycle of an object of a class within a class.
Note that the life cycle of the static object is consistent with the life cycle of the program, so as long as the object is initialized at the beginning of the main function, the singleton object can be obtained normally when the program is running.
The disadvantage of this method is that it is impossible to decide when to destroy the singleton object.

#include <iostream>
#include <exception>

class SingletonClass {
    
    
public:
	//单例对象的全局访问点
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//初始化单例对象
	static void constructInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
			static SingletonClass_Helper helper;  //利用此对象的生命周期来自动销毁单例对象
		}
		return;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //单例对象的声明

	//内部类,用于辅助销毁单例对象
	class SingletonClass_Helper {
    
    
	public:
		~SingletonClass_Helper() {
    
    
			delete SingletonClass::m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
			m_pInstance = nullptr;
		}
	};
};

SingletonClass* SingletonClass::m_pInstance = nullptr;  //单例对象的定义

int main() {
    
    
	std::cout << "main() start" << std::endl;

	{
    
    
		std::cout << "Scope start" << std::endl;

		//创建单例对象
		SingletonClass::constructInstance();

		//...

		//多次调用,获取的都是同一个对象
		SingletonClass* obj1 = SingletonClass::getInstance();
		SingletonClass* obj2 = SingletonClass::getInstance();
		SingletonClass* obj3 = SingletonClass::getInstance();

		//...

		std::cout << "Scope end" << std::endl;
	}  //出作用域,static SingletonClass_Helper helper;也不会被销毁,因此单例也不会被销毁

	//依然可以获得单例对象
	SingletonClass* obj1 = SingletonClass::getInstance();
	SingletonClass* obj2 = SingletonClass::getInstance();
	SingletonClass* obj3 = SingletonClass::getInstance();

	std::cout << "main() end" << std::endl;

	return 0;
}

输出:
main() start
Scope start
SingletonClass的单例对象被创建
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
Scope end
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
对象的地址为 00000278654695F0
main() end
SingletonClass的单例对象被销毁

4. C++11 uses the lazy singleton mode implemented by static local objects

For the lazy singleton, it is necessary to consider the situation that the global access point is called simultaneously under multiple threads, but the singleton object has not been created (that is, the first time it is called). In this case, the singleton object may be created twice. The logic is as follows:
insert image description here

  • Lazy singleton code with thread safety issues:
#include <iostream>
#include <thread>

class SingletonClass {
    
    
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new SingletonClass();
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}
private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //static data member,其本质是global object
};

SingletonClass* SingletonClass::m_pInstance = nullptr;

//线程初始化函数
void func() {
    
    
	SingletonClass::getInstance();
	return;
}

int main() {
    
    
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

可能的输出:
SingletonClass的单例对象被创建
单例对象的地址为 000001750F380410
SingletonClass的单例对象被创建
单例对象的地址为 000001750F3802B0
SingletonClass的单例对象被销毁

Before C++11, this problem is generally solved by adding a mutex. Of course, this will make code writing more troublesome. The logic at this time is as follows:
insert image description here

  • Lazy singleton code that uses mutexes to ensure thread safety (here uses the technique of double locking):
#include <iostream>
#include <thread>
#include <mutex>

class SingletonClass {
    
    
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
      //双重锁定 A
			std::unique_lock<std::mutex> my_unique_lock(m_mutex);   //自动上锁解锁
			if (m_pInstance == nullptr) {
    
      //双重锁定 B
				m_pInstance = new SingletonClass();
			}
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}
private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;

	static SingletonClass* m_pInstance;  //static data member,其本质是global object
	static std::mutex m_mutex;  //互斥量
};

SingletonClass* SingletonClass::m_pInstance = nullptr;
std::mutex SingletonClass::m_mutex;

//线程初始化函数
void func() {
    
    
	SingletonClass::getInstance();
	return;
}

int main() {
    
    
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	//最后要手动销毁单例对象
	SingletonClass::destoryInstance();

	return 0;
}

输出:
SingletonClass的单例对象被创建
单例对象的地址为 0000027A7A2502C0
单例对象的地址为 0000027A7A2502C0
SingletonClass的单例对象被销毁

The C++11 standard guarantees that if multiple threads attempt to initialize the same static local object concurrently , the initialization happens strictly once. Reference
It should be noted that what is guaranteed here is a static local object , but not a static member variable , so the singleton mode implemented in 1, 2, and 3 is not thread-safe when creating objects (of course, because the designs of 1, 2, and 3 are all hungry singletons, this problem does not exist), because the singleton object is static SingletonClass* m_pInstance;.

  • Lazy singleton code implemented using C++11 static local objects:
#include <iostream>
#include <thread>

class SingletonClass {
    
    
public:
	//这样的接口实际是一个懒汉单例
	static SingletonClass& getInstance() {
    
    
		static SingletonClass instance;  //function-local static object
		return instance;
	}

private:
	//构造函数
	SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被创建" << std::endl;
	}

	//析构函数
	~SingletonClass() {
    
    
		std::cout << "SingletonClass的单例对象被销毁" << std::endl;
	}

	//禁止拷贝
	SingletonClass(const SingletonClass&) = delete;
	SingletonClass operator=(const SingletonClass&) = delete;
};

//线程初始化函数
void func() {
    
    
	SingletonClass::getInstance();
	return;
}

int main() {
    
    
	//多个线程同时去尝试创建单例对象
	std::thread th1(func);
	std::thread th2(func);

	th1.join();
	th2.join();

	return 0;
}

输出:
SingletonClass的单例对象被创建
SingletonClass的单例对象被销毁

It is also very simple to change the lazy singleton to the hungry singleton, just make sure to get the singleton object once in the main function.

Summary of 1, 2, 3, and 4 implementations

After the C++11 standard, the implementation of 4 is more popular, and it can generally be used in the way of 4.
But the implementation of 2 is also very good, the interface design is clear, and the hungry man mode is a recommended way to use it.

5. Use templates to implement a more general hungry man singleton mode

Before C++11, the parameters of templates were fixed, so the common practice was to only adapt to constructors with less than 6 parameters.
code show as below:

#include <iostream>

template <typename T>  //模板参数应当是一个类类型
class Singleton {
    
    
public:
	//单例对象的全局访问点
	//因为不需要参数了,所以可以设计的很简单
	static T* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
      //单例对象未创建,说明代码逻辑存在问题
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象 支持0个参数
	static void constructInstance() {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T();
		}
		return;
	}
	
	//创建单例对象 支持1个参数
	template <typename T0>
	static void constructInstance(T0 arg0) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0);
		}
		return;
	}

	//创建单例对象 支持2个参数
	template <typename T0, typename T1>
	static void constructInstance(T0 arg0, T1 arg1) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1);
		}
		return;
	}

	//创建单例对象 支持3个参数
	template <typename T0, typename T1, typename T2>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2);
		}
		return;
	}

	//创建单例对象 支持4个参数
	template <typename T0, typename T1, typename T2, typename T3>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2, arg3);
		}
		return;
	}

	//创建单例对象 支持5个参数
	template <typename T0, typename T1, typename T2, typename T3, typename T4>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2, arg3, arg4);
		}
		return;
	}

	//创建单例对象 支持6个参数
	template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
	static void constructInstance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) {
    
    
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(arg0, arg1, arg2, arg3, arg4, arg5);
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	Singleton() = default;
	~Singleton() = default;
	//禁止拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

//测试部分
//类A,构造函数无参
struct A {
    
    
	A() {
    
    
		std::cout << "A类对象被创建" << std::endl;
	}

	~A() {
    
    
		std::cout << "A类对象被销毁" << std::endl;
	}
};

//类B,构造函数有参数
struct B {
    
    
	B(int x, int y, int z) {
    
    
		std::cout << "B类对象被创建" << std::endl;
	}

	~B() {
    
    
		std::cout << "B类对象被销毁" << std::endl;
	}
};

int main() {
    
    
	//创建单例对象
	Singleton<A>::constructInstance();  //创建A类对象单例
	Singleton<B>::constructInstance(1, 2, 3);  //创建B类对象单例

	//获取单例对象
	auto obj_a1 = Singleton<A>::getInstance();
	auto obj_a2 = Singleton<A>::getInstance();

	auto obj_b1 = Singleton<B>::getInstance();
	auto obj_b2 = Singleton<B>::getInstance();

	//销毁单例对象
	Singleton<A>::destoryInstance();
	Singleton<B>::destoryInstance();

	return 0;
}

输出:
A类对象被创建
B类对象被创建
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D02E0
单例对象的地址为 000001BA054D0400
单例对象的地址为 000001BA054D0400
A类对象被销毁
B类对象被销毁

After C++11, it was introduced 可变长模板、万能引用、完美转发, and using these technologies, very general templates can be written.
code show as below:

#include <iostream>

template <typename T>  //模板参数应当是一个类类型
class Singleton {
    
    
public:
	//单例对象的全局访问点
	//因为不需要参数了,所以可以设计的很简单
	static T* getInstance() {
    
    
		if (m_pInstance == nullptr) {
    
      //单例对象未创建,说明代码逻辑存在问题
			throw std::logic_error("this instance is not construct, please construct the instance in main() function first");
		}
		std::cout << "单例对象的地址为 " << m_pInstance << std::endl;
		return m_pInstance;
	}

	//创建单例对象 可以传入任意参数
	template <typename... Args>
	static void constructInstance(Args&&... args) {
    
      //Args&&... args是万能引用
		if (m_pInstance == nullptr) {
    
    
			m_pInstance = new T(std::forward<Args>(args)...);  //完美转发
		}
		return;
	}

	//销毁单例对象
	static void destoryInstance() {
    
    
		delete m_pInstance;  //即使单例对象没有被真正创建,但在定义时为其赋值nullptr了,因此直接delete是不会存在问题的
		m_pInstance = nullptr;
		return;
	}

private:
	Singleton() = default;
	~Singleton() = default;
	//禁止拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	static T* m_pInstance;
};

template <typename T>
T* Singleton<T>::m_pInstance = nullptr;

//测试部分
//类A,构造函数无参
struct A {
    
    
	A() {
    
    
		std::cout << "A类对象被创建" << std::endl;
	}

	~A() {
    
    
		std::cout << "A类对象被销毁" << std::endl;
	}
};

//类B,构造函数有参数
struct B {
    
    
	B(int x, int y, int z) {
    
    
		std::cout << "B类对象被创建" << std::endl;
	}

	~B() {
    
    
		std::cout << "B类对象被销毁" << std::endl;
	}
};

//类C,有带左值和带右值的构造函数
struct C {
    
    
	C(const std::string&) {
    
    
		std::cout << "C类对象被创建,参数为左值" << std::endl;
	}

	C(std::string&&) {
    
    
		std::cout << "C类对象被创建,参数为右值" << std::endl;
	}

	~C() {
    
    
		std::cout << "C类对象被销毁" << std::endl;
	}
};

int main() {
    
    
	//创建单例对象
	Singleton<A>::constructInstance();  //创建A类对象单例
	Singleton<B>::constructInstance(1, 2, 3);  //创建B类对象单例
	std::string c_arg = "hello";
	Singleton<C>::constructInstance(std::move(c_arg));  //创建C类对象单例

	//获取单例对象
	auto obj_a1 = Singleton<A>::getInstance();
	auto obj_a2 = Singleton<A>::getInstance();

	auto obj_b1 = Singleton<B>::getInstance();
	auto obj_b2 = Singleton<B>::getInstance();

	auto obj_c1 = Singleton<C>::getInstance();
	auto obj_c2 = Singleton<C>::getInstance();

	//销毁单例对象
	Singleton<A>::destoryInstance();
	Singleton<B>::destoryInstance();
	Singleton<C>::destoryInstance();

	return 0;
}

输出:
A类对象被创建
B类对象被创建
C类对象被创建,参数为右值
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E0340
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E03B0
单例对象的地址为 0000019BE71E0380
单例对象的地址为 0000019BE71E0380
A类对象被销毁
B类对象被销毁
C类对象被销毁

Guess you like

Origin blog.csdn.net/weixin_43003108/article/details/127106390