1、实现示例
例子为c++中,有一个类CThreadManager,我想把他做成单例模式,下面给出框架示例的头文件与源文件
头文件 (CThreadManager.h
):
#ifndef CTHREADMANAGER_H
#define CTHREADMANAGER_H
#include <mutex>
class CThreadManager {
public:
// 获取单例实例
static CThreadManager& getInstance();
// 删除拷贝构造函数和赋值操作符,防止复制
CThreadManager(const CThreadManager&) = delete;
CThreadManager& operator=(const CThreadManager&) = delete;
// 添加所需的成员函数
void doWork(); // 示例成员函数
private:
// 私有构造函数和析构函数
CThreadManager();
~CThreadManager();
// 静态成员变量
static CThreadManager* instance;
static std::mutex mutex_;
};
#endif // CTHREADMANAGER_H
源文件 (CThreadManager.cpp
):
#include "CThreadManager.h"
#include <iostream>
CThreadManager* CThreadManager::instance = nullptr;
std::mutex CThreadManager::mutex_;
// 获取单例实例
CThreadManager& CThreadManager::getInstance() {
if (instance == nullptr) { // 双检锁确保线程安全
std::lock_guard<std::mutex> lock(mutex_);
if (instance == nullptr) {
instance = new CThreadManager();
}
}
return *instance;
}
// 构造函数
CThreadManager::CThreadManager() {
std::cout << "CThreadManager initialized" << std::endl;
}
// 析构函数
CThreadManager::~CThreadManager() {
std::cout << "CThreadManager destroyed" << std::endl;
delete instance; // 释放资源
instance = nullptr;
}
// 示例成员函数
void CThreadManager::doWork() {
std::cout << "CThreadManager is working" << std::endl;
}
2、相关疑问
1)开头#ifndef CTHREADMANAGER_H #define CTHREADMANAGER_H的作用是什么
这些是头文件保护符,它们的作用是防止头文件被重复包含,从而避免编译错误。
现代 C++ 提供了 #pragma once
,效果与头文件保护符相同,且更简洁。
#pragma once
是一种编译器指令,在大多数主流编译器(如 GCC(Linux系统默认编译器)、Clang、MSVC)中都能正常工作。
2)获取单例使用的是static CThreadManager& getInstance();,这与static CThreadManager* getInstance();有什么区别吗
在 C++ 中,使用 static CThreadManager& getInstance()
和 static CThreadManager* getInstance()
的主要区别是返回值的类型——引用与指针。两者在语法、内存管理和使用场景上有一些细微的差别:
1. 返回值类型是引用 (CThreadManager&
)
- 引用返回:
CThreadManager& getInstance()
返回一个引用,意味着你返回的是单例对象的直接引用。在调用时,不需要解引用指针,代码更加简洁。 - 安全性:返回引用的单例通常被认为更安全,因为引用不能为
nullptr
。如果你能够访问单例对象,那它一定是有效的,不需要显式地检查是否为空。避免了因访问空指针(nullptr
)而引发的崩溃。 - 语法简洁:使用引用时,访问实例的代码更直接,不需要使用箭头
->
运算符。
示例:
CThreadManager& manager = CThreadManager::getInstance();
manager.doWork(); // 直接调用成员函数
优点:
- 引用保证了你总是能访问到有效的对象。
- 语法简洁直观。
缺点:
- 引用不能为
nullptr
,这意味着如果没有正确初始化单例,代码会出错。 - 无法显式地处理对象不存在的情况。
2. 返回值类型是指针 (CThreadManager*
)
- 指针返回:
CThreadManager* getInstance()
返回一个指针,意味着你获得的是单例对象的内存地址。你可以使用nullptr
来表示单例尚未初始化的情况。 - 灵活性:指针的返回类型在某些情况下可以提供更大的灵活性。例如,你可以返回
nullptr
来表示单例没有初始化或某些条件下的不可用状态。 - 需要显式检查空指针:调用时必须显式检查指针是否为
nullptr
,如果是空指针则不能访问对象,避免程序崩溃。
示例:
CThreadManager* manager = CThreadManager::getInstance();
if (manager) {
manager->doWork(); // 使用箭头操作符访问成员函数
}
优点:
- 可以明确地表示单例未初始化或不可用的状态(
nullptr
)。 - 可以在函数返回前检查指针是否为
nullptr
,更适合需要处理错误状态的场合。
缺点:
- 必须检查指针是否为
nullptr
,增加了代码的复杂性。 - 比起引用,指针语法稍显冗长,需要使用箭头运算符(
->
)来访问成员。
3. 推荐使用引用还是指针?
-
推荐使用引用 (
CThreadManager& getInstance()
):在实现单例模式时,通常建议返回引用,因为引用更简洁且不需要显式地处理空值。单例模式本身通常假定对象始终存在,因此引用更符合这种假设,且访问起来更直观。 -
使用指针 (
CThreadManager* getInstance()
) 适合那些需要处理单例可能尚未初始化的情况,或者当单例的存在性是可选的,返回指针可以提供更多的灵活性。
总结:
static CThreadManager& getInstance()
返回引用,通常更简洁、安全,适用于你假设单例对象始终有效的场景。static CThreadManager* getInstance()
返回指针,适用于你可能需要返回空指针来表示单例不可用的情况,或者需要处理不同状态下的初始化。
大多数情况下,使用返回引用的方式(CThreadManager& getInstance()
)会更简单且符合单例模式的惯例,除非有特殊需求需要返回指针。
3)CThreadManager(const CThreadManager&) = delete; CThreadManager& operator=(const CThreadManager&) = delete;这两句的目的是什么
这两句代码的目的是防止对象被复制或赋值,确保单例模式的唯一性。
单例模式的核心要求:
- 单例模式的核心是全局唯一性,整个程序中只允许创建一个实例。
- 如果允许通过拷贝构造或赋值操作复制单例对象,会破坏单例模式的约束,从而导致多个实例的存在。
4)CThreadManager 的构造函数与析构函数都变为私有,目的是保证只有通过getInstance()来进行构造吗
- 将 构造函数 和 析构函数 设为私有,确保外部无法直接创建或销毁单例对象,保证了单例模式的唯一性。
getInstance()
是唯一的获取实例的途径,它确保了整个程序中只有一个实例,并且实例的生命周期由程序控制。