设计模式---单例模式

101 阅读3分钟

定义

单例模式(Singleton Pattern),旨在确保一个类只有一个实例,并提供全局访问点。这种模式在需要控制类实例化数量时非常有用,比如配置管理器、日志记录器、线程池等场景。

核心要点

  • 唯一性:确保一个类只有一个实例。

  • 全局访问点:提供一个全局访问点来获取这个实例。

代码实现

在 C++ 中,单例模式有几种常见的实现方式,基本思想是将构造函数私有化,如此外部程序就不能用 new 来实例化它;同时添加一个名为 getInstance()public 方法,这个方法的目的是返回一个类实例,方法中做是否存在实例的判断,如果没有被实例化过,调用构造函数 new 出这个实例:

1. 线程不安全的懒汉式(Lazy Initialization)

这种实现方式在第一次请求实例时创建实例。

class Singleton
{
private:
	static Singleton* instance;		// 单例实例指针
	Singleton() {}					// 私有构造函数

public:
	static Singleton* getInstance()
	{
		if (!instance)
			instance = new Singleton();	// 延迟实例化

		return instance;
	}
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;
  • 优点:简单直接,延迟初始化,只有在第一次调用 getInstance 时才创建实例。

  • 缺点:在多线程环境下不安全,因为多个线程可能同时检测 instance 为空并创建多个实例。这个问题可以通过引入锁机制来解决,但会影响性能。

2. 线程安全的懒汉式(使用互斥锁)

在多线程的环境下确保单例实例的唯一性。

# include <mutex>

class Singleton
{
private:
	static Singleton* instance;	// 单例实例指针
	static std::mutex mtx;		// 互斥锁
	Singleton() {}				// 私有构造函数

public:
	static Singleton* getInstance()
	{
		if (!instance)
		{
			std::lock_guard<std::mutex> lock(mtx);	// 加锁以保证线程安全

			// 双重判断 instance 是否存在
			// 当多个线程排队时,第一个线程进去实例化 instance 后出来,
			// 如果没有第二重判断,第二个线程可以再创建新的实例
			if (!instance)
			{
				instance = new Singleton();			// 延迟实例化
			}
		}
		return instance;
	}
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
  • 优点:线程安全,防止多个线程同时创建实例。

  • 缺点:每次调用 getInstance 时都需要加锁,可能影响性能,尤其是在多线程高频调用的情况下。

3. 线程安全的饿汉式(Eager Initialization)

在程序启动时创建实例,确保实例创建是线程安全的。

class Singleton
{
private:
	static Singleton instance;	// 单例实例
	Singleton() {}				// 私有构造函数

public:
	static Singleton* getInstance()
	{
		return &instance;		// 直接返回静态实例
	}
};

// 静态成员初始化
Singleton Singleton::instance;
  • 优点:线程安全,实例在程序启动时创建,不需要加锁。

  • 缺点:即使实例可能不被使用,程序启动时仍会创建。可能会浪费资源。

4. 使用 'std::call_once' (c++ 11 及以上)

保证实例的初始化只会发生一次,且线程安全。

#include <mutex>

class Singleton
{
private:
	static Singleton* instance;	// 单例实例指针
	static std::once_flag flag;	// 只执行一次的标志
	Singleton() {}				// 私有构造函数

public:
	static Singleton* getInstance()
	{
		std::call_once
		(
			flag, []()
			{
				instance = new Singleton();		// 仅初始化一次
			}
		);
		return instance;
	}
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::flag;
  • 优点:线程安全,且初始化操作只会执行一次,避免了在每次调用时加锁的性能开销。

  • 缺点:需要支持 C++11 或以上版本。

总结

  • 懒汉式:延迟初始化实例,简单但在多线程中不安全。

  • 线程安全的懒汉式:使用互斥锁保证线程安全,但可能影响性能。

  • 饿汉式:在程序启动时创建实例,线程安全但可能浪费资源。

  • std::call_once:线程安全且高效,但需要支持 C++11。