设计模式-单例模式

基于单例模式创建对象

Singleton Pattern

单例模式:一个类只产生一个实例

  • 私有化类的构造函数,在类中实例化一个对象,需要使用时通过 getInstance() 方法获得
  • 节省内存资源
  • 避免多个对象引起复杂操作,例如对共享资源 (数据库、文件) 进行访问

单例模式中通过 Singleton::GetInstance() 获取单例实例而不是直接将单例实例设为 public 的原因:

  1. 控制实例的创建
    • 延迟加载(懒加载):只有在第一次需要使用单例实例时,才通过 GetInstance() 创建实例,这样可以节省系统资源
    • 初始化控制:如果单例实例需要复杂的初始化逻辑,可以通过 GetInstance() 方法进行控制,确保在合适的时间和条件下进行初始化
  2. 封装和隐藏实现细节
    • 私有化构造函数:单例模式中,构造函数是私有的,防止外部随意创建实例
    • 保护实例:通过 GetInstance() 方法返回实例,隐藏了单例实例的存储和管理逻辑,用户无法直接访问和操作实例,只能通过公有接口使用
  3. 线程安全性
    • 如果直接将实例设为公共,可能会导致多线程环境下线程不安全的问题。通过 GetInstance() 方法可以实现线程安全的单例模式,例如使用双重检查锁定(Double-Checked Locking)等技术
  4. 支持销毁操作
    • 单例模式中,通常提供一个 Destroy() 方法来销毁单例实例。如果将实例设为公共,很难控制实例的生命周期和销毁时机
  5. 遵循单例模式的设计原则
    • 单例模式的核心是确保一个类只有一个实例,并提供一个全局访问点。GetInstance() 方法作为全局访问点,符合单例模式的设计原则

Implementation

核心区别:线程安全、延迟加载、性能优化等

  1. 饿汉模式(Eager Initialization)
    • 在类加载时就立即初始化单例对象,线程安全,但实例未被使用也会创建,且无法延迟加载
    • 空间换时间
    • 基本实现方式:静态局部变量(C++11)
    • 枚举单例:见于Java
  2. 懒汉模式(Lazy Initialization)
    • 在第一次被使用时才会创建实例,分为线程不安全和线程安全两种版本,支持延迟加载
    • 时间换空间
    • 基本实现方式(Meyer’s Singleton):静态局部变量
    • 双检锁(Double-Checked Locking):在多线程环境下实现懒汉式单例的高效方式,线程安全且性能较好

CPP

基本步骤

  1. 公有化静态全局访问点
  2. 直接创建私有静态单例对象 (饿汉模式)
  3. 私有化构造函数,禁止外部创建
  4. 避免拷贝和移动构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Singleton
{
public:
// 公有 static 方法,用于获取单例句柄
static Singleton& GetInstance()
{
return singleton_;
}
// 禁止拷贝和移动构造函数
Singleton(Singleton&) = delete;
Singleton(Singleton&&) = delete;

private:
// 私有化构造函数
Singleton() {}
// 私有 static 单例对象
static Singleton singleton_ ;
};

当需要访问对象地址或与其他指针接口进行交互时可返回指针

1
2
3
4
5
public:
static Singleton *GetInstance()
{
return &singleton_;
}

基本步骤

  1. 基于饿汉模式修改,单例对象采用指针存储
  2. 使用时实例化单例对象
  3. 加互斥锁保证线程安全
  4. 双检锁 (DCLP) 避免每次都访问锁资源以优化性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Singleton
{
public:
// 公有 static 方法,用于获取单例句柄
static Singleton& GetInstance()
{
// 使用时实例化对象
if (!ptr_singleton_) {
// 采用 RAII 方式加锁
lock_guard<mutex> lock(mux_);
if (!ptr_singleton_) {
ptr_singleton_ = new Singleton;
}
}
return *ptr_singleton_;
}
// 禁止拷贝和移动构造函数
Singleton(Singleton&) = delete;
Singleton(Singleton&&) = delete;

private:
// 私有化构造函数
Singleton() {}
// 私有 static 单例对象
static Singleton *ptr_singleton_;
// 私有 锁
static std::mutex mux_;
};

C++ 11 call_onece 实现方式

1
2
3
4
5
6
7
8
9
10
11
public:
static Singleton& GetInstance()
{
std::call_once(flag, []() {
ptr_singleton_ = new Singleton();
});
return *ptr_singleton_;
}

private:
static std::once_flag flag;

双检锁问题在于如果 ptr_singleton_ 尚未创建完成而另一个线程又调用时,会返回不完整的对象,需要添加内存屏障

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public:
// 公有 static 方法,用于获取单例句柄
static Singleton& GetInstance()
{
// 使用时实例化对象
if (!ptr_singleton_) {
// 采用 RAII 方式加锁
lock_guard<mutex> lock(mux_);
if (!ptr_singleton_) {
tmp = new Singleton;
// 添加内存屏障
// linux: asm volatile("" ::: "memory");
// windows: _mm_mfence();
std::atomic_thread_fence(std::memory_order_release);
ptr_singleton_ = new Singleton;
}
}
return *ptr_singleton_;
}

C++11 直接采用静态局部变量实现,会自动加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton
{
public:
// 公有 static 方法,用于获取单例句柄
static Singleton& GetInstance()
{
static Singleton singleton;
return singleton;
}

private:
// 私有化构造函数和析构函数
Singleton();
~Singleton();
// 禁止拷贝构造、拷贝赋值、移动构造、移动赋值
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton &&) = delete;
Singleton& operator=(Singleton &&) = delete;
};

下述代码未做检查,仅供参考

1
2
3
4
5
6
7
8
9
10
class Singleton {
public:
static std::shared_ptr<Singleton> getInstance() {
static std::shared_ptr<Singleton> instance(new Singleton());
return instance;
}

private:
Singleton() {}
};

Fortran

参考 https://refactoringguru.cn/design-patterns/singleton/cpp/example

  1. 公有化全局访问点
    • 为避免多次创建实例,单例类型必须私有
    • Fortran 无引用功能,返回值需要通过指针接收以避免新创建内存空间,而接收指针需要单例类型进行定义,单例类型是私有的,因此需要进行二次封装,暴露一个公共访问对象,对单例对象进行操作
    • 懒汉模式直接将 singleton_instance 修改为指针然后在 get_instance 中分配即可
  2. 创建私有单例对象
    • 对单例对象进行二次封装从而私有化以保证安全性
  3. 私有化构造函数,禁止外部创建
    • 单例对象进行了二次封装,因此构造函数可直接私有化
  4. 禁止拷贝和移动构造函数
    • 由于接收单例对象需要通过单例类型进行定义,而单例类型是私有的,因此无需禁止

以下代码只尽量确保与 CPP 形式一致,不一定是最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
module singleton_module
implicit none
private

public :: SingletonTypeClass

! 定义私有单例类型
type :: SingletonType
integer :: value ! 数据成员
contains
final :: delete_SingletonType
end type SingletonType

! 私有单例类型构造函数
interface SingletonType
module procedure new_SingletonType
end interface SingletonType

! 单例对象实例化
type(SingletonType), target, save :: singleton_

! 定义私有单例封装类
type :: SingletonTypeClass
type(SingletonType), private, pointer :: singleton
contains
procedure, public, pass :: get_instance
final :: delete_SingletonTypeClass
end type SingletonTypeClass

contains

! 单例实例的获取函数
subroutine get_instance(this)
class(SingletonTypeClass), intent(inout) :: this
continue
this%singleton => singleton_
end subroutine get_instance

! 私有单例类型的构造函数
type(SingletonType) function new_SingletonType()
end function new_SingletonType

! 单例对象的析构函数
subroutine delete_SingletonType(this)
type(SingletonType), intent(inout) :: this
end subroutine delete_SingletonType

! 单例封装类的析构函数
subroutine delete_SingletonTypeClass(this)
type(SingletonTypeClass), intent(inout) :: this
end subroutine delete_SingletonTypeClass

end module singleton_module

可通过 Fortran 2008 critical 关键字实现互斥锁,保证线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

module singleton_module
implicit none
private

public :: SingletonTypeClass

! 定义私有单例类型
type :: SingletonType
integer :: value ! 数据成员
contains
final :: delete_SingletonType
end type SingletonType

! 私有单例类型构造函数
interface SingletonType
module procedure new_SingletonType
end interface SingletonType

! 单例对象实例化
type(SingletonType), pointer, save :: singleton_ => null()

! 定义私有单例封装类
type :: SingletonTypeClass
type(SingletonType), private, pointer :: singleton
contains
procedure, public, pass :: get_instance
final :: delete_SingletonTypeClass
end type SingletonTypeClass

contains

! 单例实例的获取函数
subroutine get_instance(this)
class(SingletonTypeClass), intent(inout) :: this
continue
if (.not. associated(singleton_)) then
singleton_ => new_SingletonType()
end if
this%singleton => singleton_
end subroutine get_instance

! 私有单例类型的构造函数
function new_SingletonType() result(Singleton)
type(SingletonType), pointer :: Singleton
continue
allocate(Singleton)
end function new_SingletonType

! 单例对象的析构函数
subroutine delete_SingletonType(this)
type(SingletonType), intent(inout) :: this
end subroutine delete_SingletonType

! 单例封装类的析构函数
subroutine delete_SingletonTypeClass(this)
type(SingletonTypeClass), intent(inout) :: this
continue
nullify(this%singleton)
end subroutine delete_SingletonTypeClass

end module singleton_module

Reference

UESTC | 【设计模式】C++对单例模式实现的总结
夏曹俊 | C++单例模式
Zijian | 单例模式
Lion Long | 7 种单例模式实现方法大
YOU | 枚举单例
Fortran 单例模式

0%