Post

单例模式

需求背景

一般在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
This post is licensed under CC BY 4.0 by the author.