C++单例
C++单例
C++ 单例模式
- Singleton 只允许被实例化一次,用于组织一系列全局的函数或者变量,与 namespace 很像。例子:随机数产生的类、渲染器类。
- C++ 中的单例只是一种组织一堆全局变量和静态函数的方法
- C++ 实现单例的基本方法:
- 将构造函数设为私有,因为单例类不能有第二个实例
- 提供一个静态访问该类的方法:设一个私有的静态的实例,并且在类外将其定义! 然后用一个静态函数返回其引用 or 指针,便可正常使用了
- 为了安全,标记拷贝构造函数为 delete(删除拷贝构造函数)
C++ 单例模板代码:
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
#include "stdafx.h"
#include "SingleTon.h"
class SingleTon
{
// SingleTon(const SingleTon&) = delete; // 删除拷贝构造函数
SingleTon(const SingleTon&) = delete; // 删除拷贝构造函数
SingleTon& operator=(const SingleTon&) = delete; // 删除拷贝赋值运算符
public:
static SingleTon& get()
{
return m_temp;
}
// 局部静态变量 instance 确保了只有一个 SingleTon 实例会被创建,
// 并且仅在第一次调用 get 方法时进行初始化。这个实现无需在类外单独定义和初始化静态成员,并且它是线程安全的(在 C++11 模型中)。
// 如果您的编译器和标准库实现支持 C++11,那么局部静态变量的初始化是线程安全的。
//static SingleTon& get() {
// static SingleTon instance; // 局部静态变量,只初始化一次
// return instance;
//}
void Function()
{
std::cout << "SingleTon Function()" << std::endl;
}
private:
SingleTon() {};// 构造函数私有
static SingleTon m_temp; // 类静态成员 m_temp 必须在类的外部定义和初始化
};
// 单例实例
SingleTon SingleTon::m_temp; // 如果使用静态成员变量的单例实现,不定义编译报错
void SingleTonTest::test_singleton()
{
// SingleTon temp2 = SingleTon::get(); //会报错,因为无法复制了
SingleTon& temp2 = SingleTon::get();
// SingleTon::get().Function(); //这般使用便可
temp2.Function();
}
// 测试:
int main() {
//SingleTon temp2 = SingleTon::get(); //会报错,因为无法复制了
SingleTon& temp2 = SingleTon::get(); //会报错,因为无法复制了
// SingleTon::get().Function(); //这般使用便可
temp2.Function();
}
示例 :随机数类
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
#include "stdafx.h"
class Random
{
public:
Random(const Random&) = delete; // 删除拷贝构造函数
Random& operator=(const Random&) = delete; // 删除拷贝赋值运算符
static Random& get()
{
static Random instance;
return instance;
}
static float getFloat()
{
// return m_random; // m_random是非静态成员
return get().randomProxy();
}
private:
Random() {}
float m_random = 0.5f;
float randomProxy()
{
return m_random;
}
};
// 与namespace很像
namespace RandomClass {
static float s_RandomGenerator = 0.5f;
static float Float() { return s_RandomGenerator; }
}
int main()
{
float randomNum = Random::Float();
std::cout<<randomNum<<std::endl;
std::cin.get();
}
单例的各种实现
全局静态单例
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
// Singleton.h头文件
/ 方式1:全局变量的方式实现单例模式
// 单例模式:确保一个类只有一个实例,并提供一个全局访问点
class Singleton
{
public:
static Singleton &getInstance() // 获取实例的方法,是静态的,所以可以直接通过类名调用
{
return instance;
}
private:
Singleton() {} // 默认构造函数是私有的,这意味着它不能从外部调用
Singleton(const Singleton &) = delete; // 删除拷贝构造函数,防止复制,这样就不会有两个实例了
Singleton &operator=(const Singleton &) = delete; // 删除赋值构造函数,防止赋值,这样就不会有两个实例了
Singleton(Singleton &&) = delete; // 删除移动构造函数,防止移动,这样就不会有两个实例了
static Singleton instance; // 静态实例,这样就可以在外部访问了
~Singleton() // 析构函数是私有的,这意味着它不能从外部调用
{
std::cout << "~Singleton" << std::endl;
}
};
// Singleton.cpp
#include "Singleton.h"
Singleton Singleton::instance; // 静态实例,这样就可以在外部访问了;不定义的话会报错:main.cpp:(.text+0x1c): undefined reference to `Singleton::instance'
// 测试:
void testSingleton1()
{
const Singleton &s1 = Singleton::getInstance();
// const Singleton &s2 = std::move(s1);
Singleton &s2 = Singleton::getInstance();
if (&s1 == &s2)
{
std::cout << "Singleton s1 and s2 are the same instance." << &s1 << ":" << &s2 << std::endl;
}
else
{
std::cout << "Singleton s1 and s2 are different instances." << std::endl;
}
}
// 结果是相等
Singleton 类的 instance 是一个静态成员变量。静态成员变量的内存是分配在程序的全局/静态内存区域,而不是在堆或栈上。
全局/静态内存区通常用于存储全局变量、静态变量、常量字符串和其它在程序整个生命周期都存在的数据。
- 全局/静态内存区:在程序启动时分配,在程序结束时释放。
Singleton::instance
的生命周期从程序开始到程序结束,这时会调用 Singleton 的析构函数。
延迟初始化,动态分配的静态实例
如果你想延迟初始化,可以在首次使用时创建实例,并在程序结束时销毁。
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
// 头文件
class Singleton2
{
public:
static Singleton2 &getInstance() // 获取实例的方法,是静态的,所以可以直接通过类名调用
{
if (instance == nullptr) // 如果实例为空,就创建一个实例
{
instance = new Singleton2(); // 创建实例,这里是动态分配的
atexit(destroy); // 注册销毁函数,确保程序结束时销毁实例
}
return *instance; // 返回实例,这里是引用,所以返回的是实例的引用
}
private:
Singleton2() {} // 默认构造函数是私有的,这意味着它不能从外部调用
Singleton2(const Singleton2 &) = delete; // 删除拷贝构造函数,防止复制,这样就不会有两个实例了
Singleton2 &operator=(const Singleton2 &) = delete; // 删除赋值构造函数,防止赋值,这样就不会有两个实例了
Singleton2(Singleton2 &&) = delete; // 删除移动构造函数,防止移动,这样就不会有两个实例了
static void destroy()
{
if (instance != nullptr)
{
delete instance;
instance = nullptr;
}
}
static Singleton2 *instance; // 静态实例,这样就可以在外部访问了
~Singleton2() // 析构函数是私有的,这意味着它不能从外部调用
{
std::cout << "~Singleton2" << std::endl;
// if (instance != nullptr)
// {
// delete instance;
// instance = nullptr;
// }
}
};
// cpp
Singleton2 *Singleton2::instance = nullptr; // 静态实例,这样就可以在外部访问了;不定义的话会报错:main.cpp:(.text+0x1c): undefined reference to `Singleton::instance2'
// 测试
void testSingleton2()
{
Singleton2 &s1 = Singleton2::getInstance();
Singleton2 &s2 = Singleton2::getInstance();
if (&s1 == &s2)
{
std::cout << "Singleton2 s1 and s2 are the same instance." << &s1 << ":" << &s2 << std::endl;
}
else
{
std::cout << "Singleton2 s1 and s2 are different instances." << std::endl;
}
}
// 结果是相等
- 相比之下,第二种实现方式中单例是使用 new 创建的,它的内存分配发生在堆上。
- 这需要手动管理内存,意味着你必须在程序中的某个时点使用 delete 来释放内存,或者在程序退出前通过
atexit()
注册的函数来释放内存。
C++11 后的局部静态变量
局部静态变量,分配在全局内存区
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
// 头文件
class Singleton3
{
public:
static Singleton3 &getInstance() // 获取实例的方法,是静态的,所以可以直接通过类名调用
{
static Singleton3 instance; // 局部静态变量,这样就可以确保线程安全
return instance;
}
private:
Singleton3() {} // 默认构造函数是私有的,这意味着它不能从外部调用
Singleton3(const Singleton3 &) = delete; // 删除拷贝构造函数,防止复制,这样就不会有两个实例了
Singleton3 &operator=(const Singleton3 &) = delete; // 删除赋值构造函数,防止赋值,这样就不会有两个实例了
Singleton3(Singleton3 &&) = delete; // 删除移动构造函数,防止移动,这样就不会有两个实例了
~Singleton3() // 析构函数是私有的,这意味着它不能从外部调用
{
std::cout << "~Singleton3" << std::endl;
}
};
// cpp文件
// 不需要在外定义静态变量
// 测试
void testSingleton3()
{
Singleton3 &s1 = Singleton3::getInstance();
Singleton3 &s2 = Singleton3::getInstance();
if (&s1 == &s2)
{
std::cout << "Singleton3 s1 and s2 are the same instance." << &s1 << ":" << &s2 << std::endl;
}
else
{
std::cout << "Singleton3 s1 and s2 are different instances." << std::endl;
}
}
// 结果相等
- 这种方式是线程安全的,因为局部静态变量只会被初始化一次,即使在多线程环境下也是如此;这种方式是 C++11 之后的特性,之前的版本不支持,因为 C++11 之前的版本不保证局部静态变量的线程安全性。
- 利用局部静态变量的线程安全(并发安全)特性,这是因为 C++11 保证了局部静态变量在首次初始化时的线程安全性。
- 但是,这种方式有一个缺点,就是不能在程序结束时销毁实例,因为局部静态变量的生命周期是从第一次调用开始到程序结束。
- 为了解决这个问题,我们可以使用局部静态变量的线程安全单例模式的变种,即使用局部静态变量,但是使用智能指针来管理实例的生命周期。
局部静态变量,分配在堆上
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
// 头文件
#include <memory>
class Singleton3_1
{
public:
static Singleton3_1 &getInstance()
{
static Singleton3_1 *instance = new Singleton3_1(); // 局部静态变量,这样就可以确保线程安全
// 使用智能指针来管理实例的生命周期,这样就可以在程序结束时销毁实例;这里使用了 lambda 表达式,来自定义销毁函数
static std::shared_ptr<Singleton3_1> s{
instance, [](Singleton3_1 *p)
{
delete p;
}};
return *instance; // 返回实例,这里是引用,所以返回的是实例的引用
}
private:
Singleton3_1() {} // 默认构造函数是私有的,这意味着它不能从外部调用
Singleton3_1(const Singleton3_1 &) = delete; // 删除拷贝构造函数,防止复制,这样就不会有两个实例了
Singleton3_1 &operator=(const Singleton3_1 &) = delete; // 删除赋值构造函数,防止赋值,这样就不会有两个实例了
Singleton3_1(Singleton3_1 &&) = delete; // 删除移动构造函数,防止移动,这样就不会有两个实例了
~Singleton3_1() // 析构函数是私有的,这意味着它不能从外部调用
{
std::cout << "~Singleton3_1" << std::endl;
}
};
// cpp
// 无
// 测试
void testSingleton3_1()
{
Singleton3_1 &s1 = Singleton3_1::getInstance();
Singleton3_1 &s2 = Singleton3_1::getInstance();
if (&s1 == &s2)
{
std::cout << "Singleton3_1 s1 and s2 are the same instance." << &s1 << ":" << &s2 << std::endl;
}
else
{
std::cout << "Singleton3_1 s1 and s2 are different instances." << std::endl;
}
}
// 结果相等
在本例中,使用了智能指针 std::shared_ptr
来确保 Singleton
实例会被正确删除。C++11 保证了静态局部变量(在函数内部的静态变量)的初始化是线程安全的,这样做即使用了这一特性,又避免了直接利用静态局部作用域。
局部静态变量,double check
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
// 头文件
// 方式4: 使用局部静态变量的线程安全单例模式,使用局部静态变量,但是使用双检查锁定
#include <mutex>
class Singleton4
{
public:
static Singleton4 &getInstance() // 获取实例的方法,是静态的,所以可以直接通过类名调用
{
if (instance == nullptr) // 如果实例为空,就创建一个实例
{
std::lock_guard<std::mutex> lock(mutex); // 加锁
if (instance == nullptr) // 双检查锁定
{
instance = new Singleton4(); // 创建实例,这里是动态分配的
atexit(destroy); // 注册销毁函数,确保程序结束时销毁实例
}
}
return *instance; // 返回实例,这里是引用,所以返回的是实例的引用
}
private:
Singleton4() {} // 默认构造函数是私有的,这意味着它不能从外部调用
Singleton4(const Singleton4 &) = delete; // 删除拷贝构造函数,防止复制,这样就不会有两个实例了
Singleton4 &operator=(const Singleton4 &) = delete; // 删除赋值构造函数,防止赋值,这样就不会有两个实例了
// Singleton4(Singleton4 &&) = delete; // 删除移动构造函数,防止移动,这样就不会有两个实例了
static void destroy()
{
if (instance != nullptr)
{
delete instance;
instance = nullptr;
}
}
static Singleton4 *instance; // 静态实例,这样就可以在外部访问了
static std::mutex mutex; // 互斥锁
~Singleton4() // 析构函数是私有的,这意味着它不能从外部调用
{
std::cout << "~Singleton4" << std::endl;
}
};
// cpp
std::mutex Singleton4::mutex; // 声声明互斥锁
Singleton4 *Singleton4::instance = nullptr; // 静态实例,这样就可以在外部访问了;不定义的话会报错:main.cpp:(.text+0x1c): undefined reference to `Singleton::instance'
// 测试
void testSingleton4()
{
Singleton4 &s1 = Singleton4::getInstance();
Singleton4 &s2 = Singleton4::getInstance();
if (&s1 == &s2)
{
std::cout << "Singleton4 s1 and s2 are the same instance." << &s1 << ":" << &s2 << std::endl;
}
else
{
std::cout << "Singleton4 s1 and s2 are different instances." << std::endl;
}
}
// 结果是相等的
这种方式,使用局部静态变量,但是使用双检查锁定,这样就可以确保线程安全了。
本文由作者按照 CC BY 4.0 进行授权