0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么C++单例模式不能直接全部使用static变量和static函数呢?

Linux爱好者 来源:Linux爱好者 作者:Linux爱好者 2022-06-05 14:14 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

开场

前段时间在知乎回答了这样一个问题:

为什么C++单例模式不能直接全部使用 static变量和 static函数呢?如果全部使用 static的话,是不是也不会有多线程的问题了?而且“类型::方法”的访问方式比起先getInstance()再访问难道不是更加简单清晰吗?

(还是说是为了附和 “单例” 这样一个字面上的意思)

//大概这个样子
classSingleton{
public:
staticvoidon(){Singleton::isOn=true;}
staticvoidoff(){Singleton::isOn=false;}
staticboolstate(){returnSingleton::isOn;}
private:
staticboolisOn;
};

这可能是很多C++学习者都会有的疑惑,下面是我的回答。

正文

通过getInstance()函数获取单例对象,这种模式的关键之处不是在于强迫你用函数来获取对象。关键之处是让static对象定义在函数内部,变成局部static变量。看下这种实现方式的经典demo:

classSingleton{
public:
staticSingleton&getInstance(){
staticSingletoninst;
returninst;
}
Singleton(constSingleton&)=delete;
Singleton&operator=(constSingleton&)=delete;

//其他数据函数
//...

private:
Singleton(){...}
//其他数据成员
//...
};

学名是:Meyers' Singleton。没错,也就是说这是Scott Meyers最早提出来的C++单例模式的推荐写法。

注意这种单例写法需要C++11。因为是从C++11标准才开始规定 static变量是线程安全的。也就是说无需我们自己写加锁保护的代码,编译器能够帮我们做到。

所以C++程序员们不要在读完Java单例模式的资料之后,在C++程序中写double check或volatile了!

如果是把 static对象定义成 Singleton的私有static成员变量,然后getInstance()去返回这个成员即:

classSingleton{
public:
staticSingleton&getInstance(){
returninst;
}
Singleton(constSingleton&)=delete;
Singleton&operator=(constSingleton&)=delete;

//其他数据函数
//...

private:
Singleton(){...}
staticSingletoninst;
//其他数据成员
//...
};
SingletonSingleton::inst;

虽然它也是 先getInstance()再访问,但这种不是Meyers' Singleton


那么为什么Meyers推荐的是第一种的呢?

原因是这解决了一类重要问题,那就是static变量的初始化顺序的问题。

C++只能保证在同一个文件中声明的static变量的初始化顺序与其变量声明的顺序一致。但是不能保证不同的文件中的static变量的初始化顺序。

然后对于单例模式而言,不同的单例对象之间进行调用也是常见的场景。比如我有一个单例,存储了程序启动时加载的配置文件的内容。另外有一个单例,掌管着一个全局唯一的日志管理器。在日志管理初始化的时候,要通过配置文件的单例对象来获取到某个配置项,实现日志打印。

这时候两个单例在不同文件中各自实现,很有可能在日志管理器的单例使用配置文件单例的时候,配置文件的单例对象是没有被初始化的。这个未初始化可能产生的风险指的是C++变量的未初始化,而不是说配置文件未加载的之类业务逻辑上的未初始化导致的问题。

Meyers' Singleton写法中,单例对象是第一次访问的时候(也就是第一次调用getInstance()函数的时候)才初始化的,但也是恰恰因为如此,因而能保证如果没有初始化,在该函数调用的时候,是能完成初始化的。所以先getInstance()再访问 这种形式的单例 其关键并不是在于这个形式。而是在于其内容,局部static变量能保证通过函数来获取static变量的时候,该函数返回的对象是肯定完成了初始化的!

讲到这,我们对Meyers' Singleton的盲目鼓吹也需冷静一下,因为C++同样能保证所有文件内(非函数内)的static变量在main()函数开始运行之后肯定是都能做完初始化的。所以如果你是在main()函数运行之后,用日志管理器的单例访问配置文件的单例,那么其实也是没有问题的… 这就引出Meyers' Singleton的第二个优势,那就是当产生继承的时候。如果出现继承,这种写法中:

classSingleton{
public:
staticvoidon(){Singleton::isOn=true;}
staticvoidoff(){Singleton::isOn=false;}
staticboolstate(){returnSingleton::isOn;}
private:
staticboolisOn;
};

classMonitor:publicSingleton{
public:
staticvoidaddBrightness(intval){brightness+=val;}
staticvoidsubBrightness(intval){brightness-=val;}
staticintgetBrightness(){returnbrightness;}

private:
staticintbrightness;
};

如果有子类继承这一父类,来拓展成新的子类,比如Monitor显示器类有开关状态,同时扩展了一个亮度的成员。但是父子类的static成员变量是共享的,其isOn成员会有问题。

好吧,如果你说你的单例完全不会出现继承的情况,是不是就不需要写成Meyers' Singleton?我只想说,如果你一定要强加这么多限定的话,那么这种设计模式的讨论本身就没有意义。就很像是在说:我自己能够保证每个new出来的指针我都能delete掉它,所以我不需要RAII……

所谓设计模式(design pattern)、惯用法(idiom)这种老程序员的经验之谈都是让你在大多数情况下,即使你不懂其奥秘,但凡遵守了,就能避免掉很多潜在的问题。尽管这种问题并不能百分百发生。所以这倒没必要去抬杠。

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 函数
    +关注

    关注

    3

    文章

    4422

    浏览量

    67873
  • C++
    C++
    +关注

    关注

    22

    文章

    2131

    浏览量

    77419

原文标题:C++ 的单例模式为什么不直接全部使用 static,而是非要实例化一个对象?

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    C++与lua联合编程

    的出现,在经济上相当于建立了一个高度标准化的“自由贸易区”或“中转仓库”。无论 C++ 想传给 Lua 一个复杂的嵌套表,还是 Lua 想回调 C++ 的一个函数,都不需要直接面对对方
    发表于 04-19 16:27

    CONFIG_STATIC_DDR 在 LS1028ARDB 上破坏 ATF 编译的定义,怎么解决?

    。 因此,我们想使用静态 DDR 配置。 我已经按照文档进行了作,并在 platform_def.h 中定义了CONFIG_STATIC_DDR 但是这样做会破坏编译! 我使用以下步骤进行了干净
    发表于 04-09 06:09

    飞凌嵌入式ElfBoard-环境变量之添加修改环境变量putenv

    ;如果是需要添加多个环境变量,则在./test 前面放置多对 name=value 即可,用空格作为分隔。C 语言函数库中也提供了相当多的方法用于添加、修改环境变量,如 putenv(
    发表于 03-26 09:22

    keil实现cc++混合编程

    参考touchgfx生成的代码,发现了一个不需要添加--cpp11 参数的解决方法,具体操作如下。 一、创建一个空白的C文件和头文件在头文件中定义c++文件中需要调用的函数,如图所示 二、在
    发表于 01-26 08:58

    keil定义fputc函数

    中统一调用。 // 设备文件写入一个字符 static int fputc_dev(int c,FILE * stream) { const libc_device_file *dev
    发表于 01-22 08:25

    嵌入式C语言中各变量存储位置

    局部变量、局部静态变量、全局变量、全局静态变量区别如下: 局部变量: 栈区; 局部静态变量:静
    发表于 12-25 07:54

    C语言与C++的区别及联系

    创建源文件时什么都不给,默认是.cpp。 3、返回值 C语言中,如果一个函数没有指定返回值类型,默认返回int类型;C++中,如果一个函数没有返回值则必须指定为void。 4、参
    发表于 12-24 07:23

    如何搞定嵌入式 C语言中的全局变量问题?

    时序,起承转合。但是尽量不要用来传递参数,这个很忌讳的。 2、尽量把变量的作用范围控制在使用它的模块里面,如果其他模块要访问,就开个读或写函数接口出来,严格控制访问范围。这一点,C++的private
    发表于 12-16 06:54

    C语言全局变量重点使用

    全局变量绝不会位于寄存器中。使用指针或者函数调用,可以直接修改全局变量的值。 因此,编译器不能将全局变量
    发表于 12-12 06:58

    CC++之间的联系

    1、语法兼容性: C++完全兼容C语言的语法,这意味着任何有效的C语言程序都可以直接C++编译器下编译通过。 2、底层控制:
    发表于 12-11 06:51

    C语言和C++之间的区别是什么

    区别 1、面向对象编程 (OOP): C语言是一种面向过程的语言,它强调的是通过函数将任务分解为一系列步骤进行执行。 C++C语言的基础上扩展了面向对象的特性,支持类(class)
    发表于 12-11 06:23

    常用变量的介绍

    extern:用在全局变量上表示该变量在其他文件中已经定义;用在函数上作用同全局变量static:用在全局
    发表于 11-21 07:05

    【CPKCOR-RA8D1】关于AI人脸检测移植遇到的一些问题

    : 两个不同文件实现了同名全局函数 handle_error(但参数/签名可能不同)。链接器不能接受重复符号。 修复策略: 统一保留单一全局实现(放在 common_util.c),并在其他文件中引用
    发表于 10-31 13:39

    PLC中Static和Temp变量的区别

    大家好,收到粉丝投稿,让博主讲下Static变量和Temp变量的区别,新入行的兄弟可能会对这两个概念不太能理解。
    的头像 发表于 09-24 14:51 1656次阅读
    PLC中<b class='flag-5'>Static</b>和Temp<b class='flag-5'>变量</b>的区别

    itop-3568开发板驱动开发指南-实验程序的编写

    );//传递字符串类型的变量 str1 12、static int __init parameter_init(void)//驱动入口函数 13、{ 14、static int i;
    发表于 05-19 10:26