首页 > 编程语言 >C++—单例设计模式

C++—单例设计模式

时间:2024-09-26 17:48:26浏览次数:8  
标签:std Singleton C++ 实例 线程 单例 设计模式

单例设计模式

C++中的单例设计模式是一种常用的软件设计模式,其核心目的是确保一个类仅有一个实例,并提供一个全局访问点来获取这个实例。以下是对C++单例设计模式的详细解释:

一、单例设计模式的定义

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。

二、单例设计模式的实现方式

在C++中,单例模式的实现方式多种多样,但基本思想都是将构造函数私有化,并通过一个静态方法提供类的唯一实例。以下是几种常见的实现方式:

  1. 懒汉式(线程不安全)
    • 延迟加载实例,即在需要时才创建实例。
    • 但在多线程环境下存在线程安全问题。
  2. 懒汉式(线程安全)
    • 通过加锁(如使用std::mutex)来保证在多线程环境下的线程安全。
    • 但每次访问实例时都需要加锁,可能会影响性能。
  3. 饿汉式
    • 在程序启动时立即创建实例,因此本身是线程安全的。
    • 但无论是否使用实例,都会立即创建,可能会浪费资源。
  4. 双重检查锁定(Double-Check Locking)
    • 一种优化懒汉式线程安全实现的方法。
    • 通过两次检查实例是否存在来减少加锁的次数,从而提高性能。
  5. 静态局部变量(C++11推荐)
    • 利用C++11中静态局部变量的线程安全特性来实现单例。
    • 这种方法既实现了延迟加载,又保证了线程安全。
  6. 使用智能指针
    • 将实例封装在智能指针中,如std::unique_ptrstd::shared_ptr,以自动管理内存。
  7. 使用std::call_once
    • C++11引入的std::call_once函数可以保证某个函数在程序执行期间只被调用一次。
    • 可以利用这个特性来实现线程安全的单例初始化。

三、单例设计模式的优缺点

优点

  1. 控制资源访问:确保对共享资源的独占访问。
  2. 简化全局访问:提供一个全局访问点,方便使用。
  3. 实现数据共享:可以在多个对象之间共享数据。

缺点

  1. 单例对象生命周期管理困难:单例对象通常在整个应用程序的生命周期内都存在,这可能导致资源无法及时释放。
  2. 扩展性差:如果需要创建多个实例,则需要对代码进行较大修改。
  3. 隐藏类的依赖关系:使用单例模式的类通常与其他类紧密耦合,这可能导致代码难以理解和维护。

四、单例设计模式的应用场景

单例设计模式适用于以下场景:

  1. 全局配置管理:如读取配置文件并管理配置信息。
  2. 数据库连接池:管理数据库连接,确保全局只有一个连接池。
  3. 日志记录器:确保全局只有一个日志记录器实例,用于记录应用程序的日志信息。
  4. 状态管理:在需要全局状态管理的场景中,如游戏开发中的游戏状态管理等。

总之,C++中的单例设计模式是一种非常有用的设计模式,但在使用时需要注意其优缺点和适用场景,以避免过度使用或误用导致的问题。

五、案例测试

1.懒汉式(线程安全)

#include <iostream>  
#include <mutex>  
  
class Singleton {  
private:  
    static Singleton* instance;  
    static std::mutex mtx;  
  
    Singleton() {} // 私有构造函数,防止外部直接创建实例  
  
    // 禁止拷贝构造函数和赋值运算符  
    Singleton(const Singleton&) = delete;  
    Singleton& operator=(const Singleton&) = delete;  
  
public:  
    static Singleton* getInstance() {  
        std::lock_guard<std::mutex> lock(mtx); // 使用锁保护  
        if (!instance) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
  
    void doSomething() {  
        std::cout << "Doing something..." << std::endl;  
    }  
  
    // 静态成员需要在类外初始化  
    static Singleton* instance_init() {  
        return nullptr;  
    }  
    static std::mutex mtx_init() {  
        return std::mutex();  
    }  
};  
  
// 初始化静态成员  
Singleton* Singleton::instance = Singleton::instance_init();  
std::mutex Singleton::mtx = Singleton::mtx_init();  
  
int main() {  
    Singleton* s1 = Singleton::getInstance();  
    Singleton* s2 = Singleton::getInstance();  
  
    if (s1 == s2) {  
        std::cout << "s1 and s2 are the same instance." << std::endl;  
    }  
  
    s1->doSomething();  
    s2->doSomething();  
  
    return 0;  
}

注意:虽然上面的代码使用了std::mutex来保证线程安全,但在实际使用中,如果单例的创建和销毁不是性能瓶颈,并且你确信单例的创建是线程安全的(例如,在程序启动时由主线程创建),那么可能不需要额外的线程安全措施。

2.静态局部变量(C++11推荐)

#include <iostream>  
  
class Singleton {  
private:  
    Singleton() {} // 私有构造函数,防止外部直接创建实例  
  
    // 禁止拷贝构造函数和赋值运算符  
    Singleton(const Singleton&) = delete;  
    Singleton& operator=(const Singleton&) = delete;  
  
    static Singleton& getInstanceHelper() {  
        static Singleton instance; // 局部静态变量,线程安全  
        return instance;  
    }  
  
public:  
    static Singleton& getInstance() {  
        return getInstanceHelper();  
    }  
  
    void doSomething() {  
        std::cout << "Doing something..." << std::endl;  
    }  
};  
  
int main() {  
    Singleton& s1 = Singleton::getInstance();  
    Singleton& s2 = Singleton::getInstance();  
  
    // s1 和 s2 实际上是同一个实例的引用  
    std::cout << "&s1: " << &s1 << std::endl;  
    std::cout << "&s2: " << &s2 << std::endl;  
  
    s1.doSomething();  
    s2.doSomething();  
  
    return 0;  
}

在这个例子中,我使用了C++11的局部静态变量特性来确保单例的线程安全性和懒加载。这种方法既简单又高效,是推荐的实现方式之一。注意,由于我们返回的是引用而不是指针,因此不需要担心内存管理问题。

标签:std,Singleton,C++,实例,线程,单例,设计模式
From: https://blog.csdn.net/weixin_45706195/article/details/142560932

相关文章

  • 常用并发设计模式精讲
    1.优雅终止线程的设计模式思考:在一个线程T1中如何优雅的终止线程T2?正确思路:两阶段终止模式1.1两阶段终止(Two-phaseTermination)模式——优雅的终止线程两阶段终止(Two-phaseTermination)模式是一种用于优雅终止线程的设计模式。该模式的基本思想是通过两个阶段来终止......
  • C++学习,信号处理
    C++信号处理,依赖于操作系统提供的API。信号处理主要用于响应外部事件,如中断信号(如SIGINT,SIGTERM等),这些信号可以由操作系统、其他程序或用户生成。在Unix-like系统(如Linux和macOS)中,信号处理可以通过signal函数或更灵活的sigaction函数来设置。在C++程序中直接使用这些函数是......
  • C++学习,# 和 ## 运算符
    C++中,# 和 ## 是两个特殊的预处理运算符,它们主要在宏定义中使用,用于字符串化和标记粘贴(tokenpasting)操作。 # 运算符:字符串化# 运算符用于将其后的宏参数转换为一个字符串常量。如果宏参数是一个宏标识符,则它会被转换成用双引号括起来的该标识符的字符串表示。这个操......
  • Effective C++学习
    导读声明式externintx;std::size_tnumDigits(intnum);classPerson;template<typenameT>classGraphNode;签名式std::size_t(int);//这个函数获得一个int,返回一个size_t定义式intx;std::size_tnumDigits(intnum){std::size_tdigitSoFar=......
  • Python设计模式速通
    目录先导对象的事情类的事情方法面对对象程序设计的几个基本要点封装多态继承抽象组合面对对象程序设计的准则开放/封闭原则控制反转原则接口隔离原则单一职责原则替换原则规定三大模式创建型模式结构型模式行为型模式先导我们开始设计模式之前,首先第......
  • C++ -函数重载-详解
    博客主页:【夜泉_ly】本文专栏:【C++】欢迎点赞......
  • 【C++掌中宝】类和对象(一):类的定义和实例化
    文章目录引言1.什么是类?1.1类的定义1.1.1类定义格式1.1.2访问限定符1.1.3类域1.2类在编程中的作用——抽象与封装2.类的基本组成2.1成员变量:类的属性(变量)2.2成员函数:类的行为(函数)2.3完整代码示例3.什么是对象?2.1实例化2.1.1实例化概念2.1.2对象大小(内......
  • 【C++掌中宝】从std的角度来进一步了解命名空间
    文章目录前言1.什么是命名空间(namespace)?2.\<iostream\>和\<iostream.h\>的区别3.C++命名空间的三种使用方式3.1直接指定标识符3.2使用using关键字3.3使用usingnamespacestd4.为什么避免使用usingnamespacestd5.命名空间冲突与解决方案6.命名空间的最......
  • 【C++】线程池
    C++线程池1.什么是线程池?解决什么问题?C++线程池(ThreadPool)的出现主要是为了解决以下几个问题:性能:创建和销毁线程都是相对昂贵的操作,特别是在高并发场景下,频繁地创建和销毁线程会极大地降低程序的性能。通过线程池预先创建一定数量的线程并保存在内存中,可以避免频繁地创建和销......
  • 【C++】C++核心编程
    C++核心编程本阶段主要针对C++面向对象编程技术,C++中的核心和精髓。1.内存分区模型C++程序在执行时,将内存大方向分为4个区域:代码区:存放函数体的二进制代码,由操作系统进行管理全局区:存放全局变量和静态变量以及常量栈区:由编译器自动分配释放,存放函数的参数值,局部变量等堆......