单例模式
需求背景
一般在c风格的编程方式下,我们可能会对某个功能的api写成如下形式:
1
2
3
4
5
void TestFuncInit(test_module_t *module);
void TestFuncRun(test_module_t *module);
void TestFuncDeinit(test_module_t *module);
一般api接口不允许改变,但这时又需要在init-run-deinit中间新增传参,这时会很自然的想到使用全局变量。
其实面对这种情况,C++中使用单例模式会显得更加优雅。
功能介绍
单例模式是指在整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。
单例模式可以分为 懒汉式 和 饿汉式 ,两者之间的区别在于创建实例的时间不同。
- 懒汉式:系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。这种方式要考虑线程安全。
- 饿汉式:系统一运行,就初始化创建实例,当需要时,直接调用即可。这种方式本身就线程安全,没有多线程的线程安全问题。
单例类的特点可概括如下:
- 构造函数和析构函数为私有类型,目的是禁止外部构造和析构
- 拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性
- 类中有一个获取实例的静态方法,可以全局访问
代码实现
懒汉式单例
使用互斥锁保证线程安全,如果不需要考虑线程安全问题可以去掉互斥锁部分
此外不需要智能指针可以设计为返回普通指针,但注意如果使用普通指针返回,不能返回指针的引用,否则存在外部被修改的风险!!此外需要新增一个delete函数在进程结束时删掉实例,不关心这一点点内存泄漏的话可以不管
以下是最传统的懒汉式实现(我用的最多的一种)
头文件:
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
// #include <mutex>
class SingleInstance
{
public:
// 获取单实例对象
static static std::shared_ptr<SingleInstance> GetInstance();
void Init();
void Run();
void Deinit();
private:
// 将其构造和析构成为私有的, 禁止外部构造和析构
SingleInstance();
~SingleInstance();
// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);
private:
// 唯一单实例对象指针
static static std::shared_ptr<SingleInstance> single_instance_;
static std::mutex mutex_;
private:
// 假设以下为我需要单例化的类内功能参数
static std::string message_;
};
源文件:
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
// 维护的单例成员(分配内存)
static static std::shared_ptr<SingleInstance> SingleInstance::single_instance_ = nullptr;
std::mutex SingleInstance::mutex;
// 维护的类内功能参数(分配内存)
static std::string message_;
static std::shared_ptr<SingleInstance> SingleInstance::GetInstance()
{
// 这里使用了两个 if 判断语句的技术称为双检锁
// 避免每次调用 GetInstance的方法都加锁
if (single_instance_ == nullptr)
{
mutex_.lock(); // 加锁
if (single_instance_ == nullptr)
{
// 返回智能指针这样写
single_instance_ = std::make_shared<SingleInstance>();
// 返回普通指针这样写
// single_instance_ = new (std::nothrow) SingleInstance(); // 防止内存不够大造成的段错误
}
mutex_.unlock();
}
return single_instance_;
}
void SingleInstance::Init()
{
std::cout << "init" << std::endl;
message_ = "我的实例内存地址是:" + std::to_string(this);
}
void SingleInstance::Run()
{
std::cout << message_ << std::endl;
}
void SingleInstance::Deinit()
{
std::cout << "deinit" << std::endl;
}
SingleInstance::SingleInstance()
{
std::cout << "构造函数" << std::endl;
}
SingleInstance::~SingleInstance()
{
std::cout << "析构函数" << std::endl;
}
以上加锁方式可以更换为
std::unique_lock<std::mutex> lock(singletonMutex);
这样就不用考虑锁的释放问题,但效率更低。按照上面的方法效率更高但一定不能忘记释放锁
调用:
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
void TestFuncInit()
{
auto my_instance = SingleInstance::GetInstance();
if (my_instance != nullptr) {
my_instance->Init();
}
// 其他init部分
...
}
void TestFuncRun()
{
auto my_instance = SingleInstance::GetInstance();
if (my_instance != nullptr) {
my_instance->Run();
}
// 其他run部分
...
}
void TestFuncDeinit()
{
auto my_instance = SingleInstance::GetInstance();
if (my_instance != nullptr) {
my_instance->Deinit();
}
// 其他deinit部分
...
}
// 这里以单线程为例,多线程类似
int main()
{
TestFuncInit();
TestFuncRun();
TestFuncDeinit();
return 0;
}
在网上看到一种现在也流行的静态局部变量的懒汉式单例模式设计,在 C++11 中,静态局部变量这种方式天然是线程安全的,不存在线程安全的问题,详见 stack overflow: Singleton instance declared as static variable of GetInstance method, is it thread-safe?
以下简写这种实现方式
头文件:
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
class Singleton
{
public:
// 获取单实例
static Singleton* GetInstance();
// 释放单实例,进程退出时调用
static void deleteInstance();
// 打印实例地址
void Print();
private:
// 将其构造和析构成为私有的, 禁止外部构造和析构
Singleton();
~Singleton();
// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
Singleton(const Singleton &signal);
const Singleton &operator=(const Singleton &signal);
private:
// 唯一单实例对象指针
static Singleton *g_pSingleton;
};
源文件:
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
Single& Single::GetInstance()
{
/**
* 局部静态特性的方式实现单实例。
* 静态局部变量只在当前函数内有效,其他函数无法访问。
* 静态局部变量只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起至程序结束止。
*/
static Single single;
return single;
}
void Single::Print()
{
std::cout << "我的实例内存地址是:" << this << std::endl;
}
Single::Single()
{
std::cout << "构造函数" << std::endl;
}
Single::~Single()
{
std::cout << "析构函数" << std::endl;
}
此外,c++11中有一个call_once函数可以很方便地实现懒汉式单例,省掉了加锁的过程,也许在c++的优化下效率更高,以下为这种方式的简单实现,<u>
很推荐使用这种方法 </u>
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
#include <iostream>
#include <memory>
#include <mutex>
class Singleton {
public:
static std::shared_ptr<Singleton> getSingleton();
void print() {
std::cout << "Hello World." << std::endl;
}
~Singleton() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
private:
Singleton() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
static std::shared_ptr<Singleton> singleton = nullptr;
static std::once_flag singletonFlag;
std::shared_ptr<Singleton> Singleton::getSingleton() {
std::call_once(singletonFlag, [&] {
singleton = std::make_shared<Singleton>();
});
return singleton;
}
饿汉式单例
饿汉式单例在源代码中不等待调用直接new了实例,因此理论上是线程安全的(但我不太敢这样用哈哈哈哈)
头文件
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
class Singleton
{
public:
// 获取单实例
static Singleton* GetInstance();
// 释放单实例,进程退出时调用
static void deleteInstance();
// 打印实例地址
void Print();
private:
// 将其构造和析构成为私有的, 禁止外部构造和析构
Singleton();
~Singleton();
// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
Singleton(const Singleton &signal);
const Singleton &operator=(const Singleton &signal);
private:
// 唯一单实例对象指针
static Singleton *g_pSingleton;
};
源文件
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
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton();
Singleton* Singleton::GetInstance()
{
return g_pSingleton;
}
void Singleton::deleteInstance()
{
if (g_pSingleton)
{
delete g_pSingleton;
g_pSingleton = nullptr;
}
}
void Singleton::Print()
{
std::cout << "我的实例内存地址是:" << this << std::endl;
}
Singleton::Singleton()
{
std::cout << "构造函数" << std::endl;
}
Singleton::~Singleton()
{
std::cout << "析构函数" << std::endl;
}
1