Bootstrap

C++ 单例模式的实现示例与相关疑问

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() 是唯一的获取实例的途径,它确保了整个程序中只有一个实例,并且实例的生命周期由程序控制。
;