欢迎光临
我们一直在努力

单例模式——C++

单例模式(Singleton Pattern)

单例模式属于创建型设计模式,核心目的就是:

一个类只允许创建一个实例,并提供一个全局访问这个实例的入口。

你之前 Qt 项目里经常使用的:

Singleton::getInstance();
Singleton::getInstance();

就是典型的单例模式。

1. 为什么需要单例?

假设程序中有一个全局的 ProgramSetting:

class ProgramSetting
{
public:
int nCenterPointX;
int nCenterPointY;
};

如果普通创建:

ProgramSetting ps1;
ProgramSetting ps2;

那么:

ps1

一份配置

ps2

另一份配置

修改:

ps1.nCenterPointX = 100;

并不会影响:

ps2.nCenterPointX

但是有些对象在整个程序中本来就应该只有一份,例如:

程序配置
日志管理器
数据库连接管理器
设备管理器
全局状态管理器
线程管理器
网络管理器

这时候可以使用单例。

2. 最简单的单例

一个经典实现:

class Singleton
{
public:
static Singleton* getInstance()
{
if (m_instance == nullptr)
{
m_instance = new Singleton;
}

return m_instance;
}

private:
Singleton() {}
~Singleton() {}

private:
static Singleton* m_instance;
};

Singleton* Singleton::m_instance = nullptr;

使用:

Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();

此时:

s1 == s2

结果是:

true

也就是说:

getInstance()


┌──────────────┐
│ Singleton │
│ 唯一对象 │
└──────────────┘
▲ ▲
│ │
s1 s2

3. 为什么构造函数要 private?

这是单例最关键的地方之一。

private:
Singleton() {}

这样外部就不能:

Singleton s;

也不能:

Singleton* s = new Singleton;

因为构造函数是 private。

外部只能:

Singleton::getInstance();

所以:

把构造函数隐藏起来,防止外部创建对象。

4. Qt/C++中更推荐的写法

现代 C++ 中可以直接使用局部静态变量:

class Singleton
{
public:
static Singleton& getInstance()
{
static Singleton instance;
return instance;
}

private:
Singleton() = default;
~Singleton() = default;

Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};

使用:

Singleton& s = Singleton::getInstance();

或者:

Singleton::getInstance().xxx();

这种方式的优点是:

不需要 new
不需要手动 delete
生命周期由 C++ 管理
C++11 起局部静态对象初始化具有线程安全保证
5. 你项目中的 Singleton<T>

你之前代码里有:

MotorStatus* ms = Singleton::getInstance();
ProgramSetting* ps = Singleton::getInstance();

这种实际上是模板单例。

可以自己实现一个:

template<class T>
class Singleton
{
public:
static T* getInstance()
{
static T instance;
return &instance;
}

private:
Singleton() = delete;
};

不过这里其实不能这么简单写,因为 T 的构造权限需要考虑。

更常见的写法:

template<typename T>
class Singleton
{
public:
static T* getInstance()
{
static T instance;
return &instance;
}
};

要求:

T

的构造函数能够被访问。

例如:

class ProgramSetting
{
public:
ProgramSetting()
{
}

int nCenterPointX = 0;
int nCenterPointY = 0;
};

然后:

ProgramSetting* ps1 =
Singleton::getInstance();

ProgramSetting* ps2 =
Singleton::getInstance();

两者指向同一个对象:

ps1 == ps2
6. Qt项目中的实际例子

例如你的程序有一个:

ProgramSetting

负责保存程序配置。

可以:

class ProgramSetting
{
public:
static ProgramSetting* instance()
{
static ProgramSetting obj;
return &obj;
}

int nCenterPointX = 0;
int nCenterPointY = 0;

private:
ProgramSetting() = default;

ProgramSetting(const ProgramSetting&) = delete;
ProgramSetting& operator=(const ProgramSetting&) = delete;
};

任何地方:

ProgramSetting* ps =
ProgramSetting::instance();

ps->nCenterPointX = 500;

其他类:

ProgramSetting* ps =
ProgramSetting::instance();

qDebug() << ps->nCenterPointX;

得到:

500

因为整个程序只有一个 ProgramSetting。

7. 单例模式的结构

可以把单例理解成三个关键点:

Singleton

┌──────────┴──────────┐
│ │
private构造函数 getInstance()
│ │
│ ▼
│ 唯一的对象
│ │
└─────────────────────┘

也就是:

① 私有构造
private:
Singleton();

防止外部:

new Singleton;
② 唯一实例
static Singleton instance;

保证只创建一个。

③ 全局访问入口
static Singleton& getInstance();

所有地方通过它获取对象。

8. 单例模式和全局变量有什么区别?

看起来很像:

GlobalData g_data;

和:

Singleton::getInstance();

但是单例可以把对象的:

创建
生命周期
访问方式
初始化

封装起来。

例如:

class LogManager
{
public:
static LogManager& instance();

void writeLog(const QString& text);

private:
LogManager();
};

外部不用关心:

什么时候创建?
什么时候销毁?
是否已经创建?

只需要:

LogManager::instance().writeLog(“hello”);
9. 单例模式的一个重要问题

单例非常方便,但不要什么东西都做成单例。

例如:

Singleton<Camera>::getInstance();
Singleton<Motor>::getInstance();
Singleton<ProgramSetting>::getInstance();
Singleton<Database>::getInstance();
Singleton<Network>::getInstance();
Singleton<Worker>::getInstance();
Singleton<Widget>::getInstance();

如果整个项目大量使用单例,会产生:

A

├── Singleton B

├── Singleton C

├── Singleton D

└── Singleton E

最后类之间产生大量隐式依赖。

例如:

void Camera::start()
{
ProgramSetting::getInstance()->xxx;

MotorStatus::getInstance()->xxx;

LogManager::getInstance()->xxx;

NetworkManager::getInstance()->xxx;
}

这个类就和很多全局对象绑定在一起了。

测试也会变得困难。

10. 单例模式和普通类最大的区别

普通类:

Camera camera1;
Camera camera2;
Camera camera3;

可以有:

Camera1
Camera2
Camera3

单例:

Camera::instance();
Camera::instance();
Camera::instance();

得到:

┌─────────────┐
│ Camera │
│ 唯一实例 │
└─────────────┘
▲ ▲ ▲
│ │ │
① ② ③

11. 单例模式和其他设计模式的关系

你最近学习的这些模式,可以这样区分:

模式主要解决的问题
工厂怎么创建对象
单例对象只能有一个
适配器接口不兼容
装饰器动态增加功能
外观隐藏复杂子系统
代理控制对象访问
桥接抽象和实现分离
组合树形结构统一处理
享元共享大量对象,节省资源
职责链请求沿链传递处理
备忘录保存/恢复对象状态

其中单例属于创建型模式。

12. 用你项目里的代码理解

You often write code like this:

MotorStatus* ms =
Singleton<MotorStatus>::getInstance();

ProgramSetting* ps =
Singleton<ProgramSetting>::getInstance();

可以把它理解成:

程序

┌──────────┴──────────┐
│ │
MotorStatus ProgramSetting
单例 单例
│ │
┌────┴────┐ ┌────┴────┐
│ │ │ │
FormA Worker FormB Camera

所有模块拿到的都是同一个 MotorStatus 和同一个 ProgramSetting。

所以你项目中:

ps->nCenterPointX

这种全局配置数据非常适合用单例来管理。

一句话记忆:

单例模式 = 把“只能有一个的对象”封装起来,并提供统一的访问入口

赞(0)
未经允许不得转载:171主机测评 » 单例模式——C++
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址