C++中的单例模式

2023-11-07

单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。

单例模式有许多种实现方法,在C++中,甚至可以直接用一个全局变量做到这一点,但这样的代码显的很不优雅。 使用全局对象能够保证方便地访问实例,但是不能保证只声明一个对象——也就是说除了一个全局实例外,仍然能创建相同类的本地实例。

《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。

单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。

定义入下:


class CSingleton
{
private:
    CSingleton()   //构造函数是私有的
    {
    }
    static CSingleton *m_pInstance;
public:
    static CSingleton * GetInstance()
    {
        if(m_pInstance == NULL)  //判断是否第一次调用
            m_pInstance = new CSingleton();
        return m_pInstance;
    }
};

用户访问唯一实例的方法只有GetInstance()成员函数。如果不通过这个函数,任何创建实例的尝试都将失败,因为类的构造函数是私有的。
GetInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的。这是一种防弹设计——所有GetInstance()之后的调用都返回相同实例的指针:

CSingleton* p1 = CSingleton :: GetInstance();
CSingleton* p2 = p1->GetInstance();
CSingleton & ref = * CSingleton :: GetInstance();

对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。

单例类CSingleton有以下特征:

  • 它有一个指向唯一实例的静态指针 p_instance ,并且是私有的;
  • 它有一个公有函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;
  • 它的构造函数是私有的,这样就不能从别处创建该类的实例;
  • 大多数时候,这样的实现都不会有问题,但p_instance 指向的空间什么时间释放呢?更严重的问题,该实例的析构函数什么时间执行?
  • 如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。
    • 可以在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。
    • 因为这样的附加代码很容易忘记,而且也很难保证在delete 之后,没有代码再调用GetInstance函数;
    • 一个妥善的方法就是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行;

我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有类的静态成员变量,就像这些静态成员也是全局变量一样,利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例,如下代码中的CGarbo类:


class CSingleton
{
private:
    CSingleton(){}
    static CSingleton *m_pInstance;
    class CGarbo   //它的唯一工作就是在析构函数中删除CSingleton的实例
    {
    public:
        ~CGarbo()
        {
            if(CSingleton::m_pInstance)
                delete CSingleton::m_pInstance;
        }
    };
    static CGarbo Garbo;  //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
    static CSingleton * GetInstance()
    {
        if(m_pInstance == NULL)  //判断是否第一次调用
            m_pInstance = new CSingleton();
        return m_pInstance;
    }
};

类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。
程序运行结束后,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例;
使用这种方法释放单例对象有以下特征:

  • 在单例类内部定义专有的嵌套类;
  • 在单例类定义私有的专门用于释放的静态成员;
  • 利用程序在结束时析构全局变量的特性,选择最终的释放时机;
  • 使用单例的代码不需要任何操作,不必关系对象的释放;

进一步讨论:

但是添加一个类的静态对象,总是让人不满意,所以有人用以下方法重新实现单例和解决它相应的问题,代码如下:

class CSingleton
{
private:
    CSingleton()   //构造函数是私有的
    {
    }
public:
    static CSingleton & GetInstance()
    {
        static CSingleton instance;   //局部静态变量
        return instance;
    }
};

使用局部静态变量,非常强大的方法,完全实现了单例的特性,而且代码量更少,也不用担心单例销毁的问题;
但是使用此种方法也会出现问题,当如下方法使用单例时问题就来了:
Singleton singleton = Singleton :: GetInstance();

这么做就出现了一个类拷贝的问题,就违背了单例的特性,产生这个问题的原因在于:编译器会为类生成一个默认的构造函数,来支持类的拷贝。

最后没有办法,我们要禁止类拷贝和类赋值,禁止程序员用这种方式来使用单例,可以显式的声明类拷贝的构造函数,和重载 = 操作符,新的单例类如下:


class CSingleton
{
private:
    CSingleton()   //构造函数是私有的
    {
    }
    CSingleton(const CSingleton &);
    CSingleton & operator = (const CSingleton &);
public:
    static CSingleton & GetInstance()
    {
        static CSingleton instance;   //局部静态变量
        return instance;
    }
};

关于
Singleton(const Singleton);Singleton & operate = (const Singleton&);函数,需要声明成私有的,并且只声明不实现。这样,如果用上面的方式来使用单例时,不管是在友元类还是其他的,编译器都是报错;


class Lock
{
private:       
    CCriticalSection m_cs;
public:
    Lock(CCriticalSection  cs) : m_cs(cs)
    {
        m_cs.Lock();
    }
    ~Lock()
    {
        m_cs.Unlock();
    }
};

class Singleton
{
private:
    Singleton();
    Singleton(const Singleton &);
    Singleton& operator = (const Singleton &);

public:
    static Singleton *Instantialize();
    static Singleton *pInstance;
    static CCriticalSection cs;
};

Singleton* Singleton::pInstance = 0;

Singleton* Singleton::Instantialize()
{
    if(pInstance == NULL)
    {   //double check
        Lock lock(cs);           //用lock实现线程安全,用资源管理类,实现异常安全
        //使用资源管理类,在抛出异常的时候,资源管理类对象会被析构,析构总是发生的无论是因为异常抛出还是语句块结束。
        if(pInstance == NULL)
        {
            pInstance = new Singleton();
        }
    }
    return pInstance;
}

之所以在Instantialize函数里面对pInstance 是否为空做了两次判断,因为该方法调用一次就产生了对象,pInstance == NULL 大部分情况下都为false,如果按照原来的方法,每次获取实例都需要加锁,效率太低。而改进的方法只需要在第一次 调用的时候加锁,可大大提高效率。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中的单例模式 的相关文章

  • C++中的namespace

    namespace中文意思是命名空间或者叫名字空间 传统的C 只有一个全局的namespace 但是由于现在的程序的规模越来越大 程序的分工越来越细 全局作用域变得越来越拥挤 每个人都可能使用相同的名字来实现不同的库 于是程序员在合并程序的
  • [原]Pro*C介绍-内嵌SQL

    Translate by Z Jingwei Document address http www db stanford edu ullman fcdb oracle or proc html Pro C介绍内嵌SQL 概要 Pro C语法
  • 范围for语句

    C 新标准提供的范围for语句 这种语句遍历给定序列中个元素并对序列中每一个值执行某种操作 其语法形式是 for declaration expression statement 其中 expression 部分是一个对象 用于表示一个序列
  • SQL 查询指定行数的数据。

    今天遇到一个关于 查询指定行数的数据 的sql查询语句问题 突然发现以前没怎么接触过 刚才想起来了 赶紧看了下文档 又上网搜了下 有了下面的东西 不知道有没有什么地方不对 oracle 先看一下文档中关于any和all的例子 很不错噢 An
  • 编写递归算法,计算二叉树叶子结点的数目。

    编写递归算法 计算二叉树叶子结点的数目 编写递归算法 计算二叉树叶子结点的数目 include stdio h 包含 getchar scanf printf include malloc h malloc 动态申请空间 函数 二叉树 结点
  • 解决“17: 错误:程序中有游离的‘\240’,\302’

    参考链接 https blog csdn net asuphy article details 54602426 执行如下命令即可 sed i s o240 o302 g dy haikang test cpp
  • 编写程序模拟完成动态分区存储管理方式的内存分配和回收。

    usr bin python coding utf 8 class Table object 空闲分区表 0 开始地址 1 长度 freeTable 占用分区表 0 程序名 1 开始地址 2 长度 useTable def init sel
  • 使用QZXing生成并解析二维码

    QZxing 是对 zxing 的一个封装 用于在 Qt 程序中加入条形码和二维码识别的功能 这里就讲讲如何编译和使用这个库 前几年 QZXing 的代码是放到 sourceforge net 上的 现在迁移到了 github com 所以
  • Lua和C++交互总结(很详细)

    出处 http blog csdn net shun fzll article details 39120965 一 lua堆栈 要理解lua和c 交互 首先要理解lua堆栈 简单来说 Lua和C c 语言通信的主要方法是一个无处不在的虚拟
  • 为何在新建STM工程中全局声明两个宏

    在uVision中新建STM32工程后 需要从STM32标准库中拷贝标准外设驱动到自己的工程目录中 此时需要在工程设置 gt C C 选项卡下的Define文本框中键入这两个全局宏定义 STM32F40 41xxx USE STDPERIP
  • lua和测试(一)

    lua做为一门高级语言 在游戏产业运用到机会越来越多了 测试掌握几门脚本语言也有一定的重要性 以下对于lua组合输入做出一些引导 测试需要掌握的关于返回数值 主要用到布尔类 前言的指引 lua的语法比较简单和清晰 学过c语言的可以很好的掌握
  • 模板的完全特例化和部分特例化

    介绍 完全特例化就是类型完全明确的版本 而部分特例化指的是 只知道是几个参数的函数而不知道参数的类型 或者是只知道是引用或者是指针类型 而不知道具体是char 还是 int 模板特例化实例1 template
  • Dev-C++之开启装逼效果

    Dev C 是个不错的C IDE 在10年前 它是很不错 在现在 它是个以界面丑陋和调试像吃粑粑这两点著称 如下图 实在是丑到离谱 丑到无法忍受 可是没办法呀 人家CCF规定比赛用这个 你个小蒟蒻吵什么 我现在就来讲讲怎么把你的Dev C
  • C++:指向类的成员的指针

    引 想必接触过C的朋友们对C语言中指针的概念已经有了深入的了解 如果初步进行了解的朋友可以看一下 C语言基础学习笔记 指针展开来讲的基本知识点包括 指针的概念 指针的定义和初始化及简单使用 指针函数和函数指针 有关指针函数和函数指针的内容上
  • visual studio 一直显示正在准备解决方案

    首先重启电脑 无法解决的情况下执行以下步骤 Kill Visual Studio Open Visual Studio without loading a solution Disable AnkhSvn as Source Control
  • 虚函数不能声明为static

    虚函数申明为static报错 class Foo public Foo default static virtual Foo int main Foo foo return 0 main cpp 10 25 error member Foo
  • C++中的并发多线程网络通讯

    C 中的并发多线程网络通讯 一 引言 C 作为一种高效且功能强大的编程语言 为开发者提供了多种工具来处理多线程和网络通信 多线程编程允许多个任务同时执行 而网络通信则是现代应用程序的基石 本文将深入探讨如何使用C 实现并发多线程网络通信 并
  • C/C++编程:令人印象深刻的高级技巧案例

    C C 编程语言在软件开发领域有着悠久的历史 由于其高效 灵活和底层访问能力 至今仍然被广泛应用 本文将介绍一些在C C 编程中令人印象深刻的高级技巧 帮助读者提升编程水平 更加高效地使用这两种强大的编程语言 一 指针运算与内存管理 C C
  • 在 OS X 上的 virtualenv 中安装 scrapy 加密时发生错误 [关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 我正在安装 scrapypip in virtualenv on OS X 10 11 当它安装密码学时 它说 buil
  • Woocommerce:添加第二个电子邮件地址不起作用,除非收件人是管理员

    我尝试了多种方法来向 Woocommerce 电子邮件添加其他收件人 但它似乎仅适用于主要收件人是管理员的测试订单 这些是我尝试过的片段 如果订单的客户是管理员 则电子邮件将发送到这两个地址 如果订单包含客户电子邮件地址 则仅发送至该电子邮

随机推荐

  • NIPS 2017

    Attention is all you need Author Unit Google Brain Google Research University of Toronto Authors Ashish Vaswani Noam Sha
  • 什么副业可以月赚1万元?做什么副业可以月入上万?

    在当前经济形势下 对于一个普通上班族而言 指望工资生活 日子肯定是过得紧巴巴的 许多人想谋求一份副业收入很正常 那么 在当前社会上 有哪些副业项目 能一个月收入一万多呢 我这里给大家推荐几个 仅供用于调研参考 1 自媒体 也许很多人会说这个
  • 决策树详解(一)

    1 决策树的概念 决策树算法以树状结构表示数据分类的结果 每个决策点实现一个具有离散输出的测试函数 记为分支 决策树的元素有 根节点 非叶子节点 分支 叶节点四种元素 其代表的含义如下图所示 决策树的工作分为两个阶段 1 训练阶段 给定训练
  • 生成以太坊系地址

    代码解析 要首先生成一个新的钱包地址 我们需要导入go ethereum crypto包 该包提供用于生成随机私钥的GenerateKey方法 privateKey err crypto GenerateKey if err nil log
  • 再论KVM超量使用

    转载自 http www sohu com a 111248295 251444 KVM超量使用一直是热门话题 前段时间发的文章 群讨论 虚拟机能否使用32个CPU 又引去了群友的激烈讨论 本文为群友根据自己的经验总结投稿 感谢这位热心的群
  • 华为 ospf 报文及邻居状态有限机

    华为 IP 路由基础 ospf 报文及邻居状态有限机 ospf协议邻居建立 一 ospf的工作机制 建立邻居 发送数据库信息 计算出最短路径 hello报文 用来建立邻居和维护邻居 邻居发现 是自动发现邻居路由器 使用的组播的地址224 0
  • docker 安装

    前提 开发环境为虚拟机Ubuntu 16 04 更换国内镜像 虚拟机是刚刚安装的 需要先更换成国内镜像源 1 首先备份原始源文件 sudo cp etc apt sources list etc apt sources list bak 2
  • RS推荐系统-LSH最近邻查找+MiniHash

    什么是最近邻查找 在推荐系统中 主要分为召回跟排序两个阶段 召回阶段 基于用户画像及场景数据从海量的视频库 百万级别 中将相关度最高的资源检索出来 作为候选集 召回阶段可以通过 粗糙 的方式召回候选item 排序阶段 基于更加精细的特征对候
  • Aspose实现word、excel、ppt转pdf

    1 工具类 AsposeUtil Component Slf4j public class AsposeUtil private static final String WORD doc docx wps wpt txt private s
  • 布隆过滤器及其实现

    简介 Bloom Filter 是由布隆 Burton Howard Bloom 在1970年提出的 loom Filter是一种空间效率很高的随机数据结构 它实际上是由一个很长的二进制向量和一系列随机映射函数组成 布隆过滤器可以用于可以快
  • kubernetes高可用集群安装(二进制安装、v1.20.2版)

    1 前言 之前文章安装 kubernetes 集群 都是使用 kubeadm 安装 然鹅很多公司也采用二进制方式搭建集群 这篇文章主要讲解 如何采用二进制包来搭建完整的高可用集群 相比使用 kubeadm 搭建 二进制搭建要繁琐很多 需要自
  • 修复Unity空白报错问题

    修复Unity空白报错问题 在升级Unity Hub之后 偶然发现Console里有几行空白的报错 看不到任何信息 由于有报错 导致修改代码无法生效 尝试重启项目 重装Unity都完全没效果 而且就算新建一个空白项目 只要添加代码就会立刻报
  • Linux操作系统原理—内核网络协议栈

    前言 本文主要记录 Linux 内核网络协议栈的运行原理 数据报文的封装与分用 封装 当应用程序用 TCP 协议传送数据时 数据首先进入内核网络协议栈中 然后逐一通过 TCP IP 协议族的每层直到被当作一串比特流送入网络 对于每一层而言
  • CASE WHEN 及 SELECT CASE WHEN的用法

    Case具有两种格式 简单Case函数和Case搜索函数 简单Case函数 CASE sex WHEN 1 THEN 男 WHEN 2 THEN 女 ELSE 其他 END Case搜索函数 CASE WHEN sex 1 THEN 男 W
  • 批量保存数据

    批量保存 批量保存 参数为对象集合 br 条件 对象属性与数据库字段完全对应 允许使用大骆驼拼写法 br 2018年4月28日下午5 30 53 throws Exception private String insert00000 Lis
  • BandZIP无广告版(v6.25)安装及禁止联网设置

    安装和设置 1 下载和安装 bandzip版本v6 25以前的都是免费且无广告的 我们从网上找到6 25版本的进行下载安装 双击 如下图进行设置 点击同意并安装 安装过程中取消自动更新 其他设置默认 安装完成后 虽然不会自动更新 但每次使用
  • C# EasyModbus xktComm Modbus 例子

    转载请注明出处 联系我 田工 15118249062 微信同号 当然先要在NuGet按照相应的dll 不是ModbusRTU报文 在RTU报文前面加了4个字节 transactionIdentifier protocolIdentifier
  • 什么是Scrum?Scrum的核心要点和精髓

    有点长 期望你能通过本文彻底了解 Scrum 我们介绍了一个非常有意思且高效的组织模式 特性团队 我们首先介绍了为什么需要特性团队 特性团队的定义 核心价值 优势 可能存在的问题以及带来的成本 接着讲述了特性团队的适用范围 开发新产品 拓展
  • alertdialog 自定义样式回调选手_把 Node.js 中的回调转换为 Promise

    每日前端夜话 第431篇 正文共 2300 字 预计阅读时间 7 分钟 介绍 在几年前 回调是 JavaScript 中实现执行异步代码的唯一方法 回调本身几乎没有什么问题 最值得注意的是 回调地狱 在 ES6 中引入了 Promise 作
  • C++中的单例模式

    单例模式也称为单件模式 单子模式 可能是使用最广泛的设计模式 其意图是保证一个类仅有一个实例 并提供一个访问它的全局访问点 该实例被所有程序模块共享 有很多地方需要这样的功能模块 如系统的日志输出 GUI应用必须是单鼠标 MODEM的联接需