定义
单例模式(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。