1. 概述

很多的知识,学习的时候理解其实并不是很深,甚至觉得是是不太必要的;而到了实际使用中遇到了,才有了比较深刻的认识。

2. 详论

2.1. 静态类型

2.1.1. 静态方法成员

比如说类的静态成员函数。从学习中我们可以知道,类的静态成员表示这个类成员直接属于类本身;无论实例化这个类对象多少次,静态成员都只是一份相同的副本。那么什么时候去使用这个特性呢?一个很简单的例子,假设我们实现了很多函数:

  1. void FunA() {}
  2. void FunB() {}
  3. void FunC() {}

这些函数如果具有相关性,都是某个类型的工具函数,那么我们可以将其封装成一个工具类,并将其方法成员都定义成静态的:

  1. class Utils {
  2. public:
  3. static void FunA() {}
  4. static void FunB() {}
  5. static void FunC() {}
  6. };

这样做的好处很多:

  1. 体现了面向对象的思想。并且,这些方法在类中本来就只需要一份就可以了,节省了程序内存。
  2. 避免在全局作用域定义函数。一般的编程认为,定义在全局作用域的变量或者方法是不太好的。
  3. 方便使用:只用记住Utils这个类的名字,就可以在IDE输入提示的帮助下快熟输入想要的函数。

2.1.2. 静态数据成员

一个顺理成章的问题就是,既然静态方法成员这么好用,那么我们使用静态数据成员也挺好的吧?一般情况下确实如此,比如我们给这个工具类定义一个静态数据成员pai:

  1. class Utils {
  2. public:
  3. static void FunA() {}
  4. static void FunB() {}
  5. static void FunC() {}
  6. static double pai;
  7. };
  8. double Utils::pai = 3.1415926;

但是有一个问题在于,简单的数据成员能够通过赋值来初始化,如果是一个比较复杂的数据成员呢?一个例子就是std::map容器数据成员,需要经过多次插入操作来初始化。这个时候只是通过赋值就很难实现了。

不仅如此,使用类的静态数据成员还会遇到一个相互依赖的问题,如参考文献2中所述。由于静态变量的初始化顺序是不定的,很可能会导致静态变量A初始化需要静态变量B,但是静态变量B却没有完成初始化,从而导致出错的问题。

2.2. 单例模式

2.2.1. 实现

C++并没有静态类和静态构造函数的概念。在参考文献1中,论述了一些用C++去实现静态构造函数,从而更加合理的去初始化静态数据成员的办法。其中一个实现是:我们需要的类按照正常的非静态成员类去设计,但是我们可以把这个类作为另一个包装类的静态成员变量,这样就能完美实现静态构造函数。

正是这个实现给了我灵感:我们想要的不是访问类的静态成员变量,而是单例模式。不想像C一样使用全局函数或者全局变量,又不想每次都去实例化一个对象,那么我们需要的是单例模式。参考文献3中给出了单例模式的最佳实践:

  1. class Singleton {
  2. public:
  3. ~Singleton() { std::cout << "destructor called!" << std::endl; }
  4. Singleton(const Singleton&) = delete;
  5. Singleton& operator=(const Singleton&) = delete;
  6. static Singleton& get_instance() {
  7. static Singleton instance;
  8. return instance;
  9. }
  10. private:
  11. Singleton() { std::cout << "constructor called!" << std::endl; }
  12. };
  13. int main() {
  14. Singleton& instance_1 = Singleton::get_instance();
  15. Singleton& instance_2 = Singleton::get_instance();
  16. return 0;
  17. }

这段代码的说明如下:

  1. 构造函数和析构函数都存在,无论多复杂的成员,都可以对数据成员初始化和释放。
  2. 构造函数时私有的,所以无法直接声明和定义。
  3. 拷贝构造函数和赋值构造函数都被删除,因此无法进行拷贝和赋值。
  4. 只能通过专门的实例化函数get_instance()进行调用。

在实例化函数get_instance()内部,实例化了一个自身的局部的静态类。静态局部变量始终存放在内存的全局数据区,只在第一次初始化,从第二次开始,它的值不会变化,是第一次调用后的结果值。并且最后,返回的是这个静态局部变量的引用。

2.2.2. 问题

无论从哪方面看,上述的单例实现,都符合单例的设计模式:全局只提供唯一一个类的实例,在任何位置都可以通过接口获取到那个唯一实例,无法拷贝也无法赋值。但是也有几个问题值得讨论。

第一个问题是,在多线程的环境下,初始化是否会造成冲突或者生成了两份实例?关于这一点不用担心,从C++11标准开始,局部静态变量的初始化是线程安全的。

第二,在参考文献4中讨论了这样一个问题:C++单例模式跨DLL是不是就是会出问题?静态变量是单个编译单元的静态变量,如果动态库和可执行文件都引用了get_instance()的实现,那么动态库和可执行文件会分别保有一份自己的实例。解决方法是要么将get_instance()放入到cpp中,要么使用DLL的模块导入导出接口的规则,也就是dllexport和dllimport。

第三,单例模式还有基于模块的实现,不过我觉得模板的实现太复杂,第二个问题就是使用模板导致的,这里就不讨论了。

3. 参考

  1. C++静态构造函数
  2. 解决静态全局变量初始化的相互依赖问题
  3. C++ 单例模式总结与剖析
  4. C++单例模式跨DLL是不是就是会出问题?

C++从静态类型到单例模式的更多相关文章

  1. 编译器开发系列--Ocelot语言6.静态类型检查

    关于"静态类型检查",想必使用C 或Java 的各位应该非常熟悉了.在此过程中将检查表达式的类型,发现类型不正确的操作时就会报错.例如结构体之间无法用+ 进行加法运算,指针和数值之 ...

  2. c# 变量,对象,静态类型,集合类的线程安全回顾

    1.变量的线程安全性与变量的作用域有关. 2.对象 对象是类型的实例 在创建对象时,会单独有内存区域存储对象的属性和方法.所以,一个类型的多个实例,在执行时,只要没有静态变量的参与,应该都是线程安全的 ...

  3. Java中静态类型检查是如何进行的

    以下内容来自维基百科,关于静态类型检查和动态类型检查的解释: 静态类型检查:基于程序的源代码来验证类型安全的过程: 动态类型检查:在程序运行期间验证类型安全的过程: Java使用静态类型检查在编译期间 ...

  4. Flow: JavaScript静态类型检查工具

    Flow: JavaScript静态类型检查工具 Flow是Facebook出品的,针对JavaScript的静态类型检查工具.其代码托管在github之上,并遵守BSD开源协议. 关于Flow 它可 ...

  5. 高性能PHP支持静态类型

    PHP+QB是一个可选的PHP虚拟机,它声称在性能上提供了数量级的提升.而负面影响就是它需要所有的内容都必须是静态类型,同时也对数组做了一些限制. 静态 类型声明 是通过PHPDoc语法的一个扩展添加 ...

  6. O-C相关-08-动态类型与静态类型

    08-动态类型与静态类型 1, 什么是动态类型和静态类型 1) 动态语言 又叫动态编程语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化.比如众所周知的EC ...

  7. c++ :OOP之静态类型与动态类型

    所谓静态类型即类型指针或引用的字面类型:而动态类型即类型指针或引用的实际类型. 这一对概念一般发生在基类和派生类之间. 如: class Base { ..... } class Derived : ...

  8. OC 动态类型和静态类型

    多态 允许不同的类定义相同的方法 动态类型 程序直到执行时才能确定所属的类 静态类型 将一个变量定义为特定类的对象时,使用的是静态形态 将一个变量定义为特定类的对象时,使用的是静态类型,在编译的时候就 ...

  9. Coding语言强弱类型且动静态类型简单解析。附图解

    话不多说,上图: (以下均以Java来说明) 对于语言的强弱类型: 1.强类型语言:通俗的点来讲,就是对于数据类型,如果开发者定义了一个int数据类型的变量,那么虚拟机就会特别坚定该变量为int,坚决 ...

随机推荐

  1. 使用Web存储API存取本地数据

    使用Web存储API TODO:本文由 赤石俊哉 翻译整理,您可以将本文自由地用于学习交流.如需用于其他用途请征得作者的同意. 原文链接:Using the Web Storage API - Moz ...

  2. HTML 初学整理

    一.HTML简介 HTML的概念 HTML是HyperText Markup Language(超文本标记语言)的简写,超文本标记语言,标准通用标记语言下的一个应用."超文本"就是 ...

  3. Python知识结构

    Python知识结构(点我) 欢迎评论提修改意见

  4. LC-24

    [24. 两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的 ...

  5. python版本共存与语法的注释

    python的多种版本共存 首先还是先下载python解释器除最高版本的另外两个版本 个人推荐的是 3.6.8和2.7.14 首先我电脑是win7系统 在计算机属性右键点开高级设置点击环境变量 将下载 ...

  6. html显示与隐藏元素的几种方式

    html显示与隐藏元素的几种方式 1.display none : 无 隐藏元素 block : 显示 转换为块级元素   不占位:当隐藏的时候元素就完全没有了.不能看见和操作该元素. 优点:为其他元 ...

  7. js字符串常用的方法总结,及其用法

    JS自带函数concat将两个或多个字符的文本组合起来,返回一个新的字符串.var a = "hello";var b = ",world";var c = a ...

  8. android软件简约记账app开发day08-时间对话框的书写+改bug,改bug

    android软件简约记账app开发day08-时间对话框的书写+改bug,改bug 绘制对话跨页面 在添加记账信息功能中,我提供了用户添加备注添加事件的功能,设计是点击时间会弹出一个时间对话框供用户 ...

  9. Sql语言学习——DDl

    DDL:操作数据库.表 1. 操作数据库:CRUD 1. C(Create):创建 * 创建数据库: * create database 数据库名称; * 创建数据库,判断不存在,再创建: * cre ...

  10. 2021.08.01 P3377 左偏树模板

    2021.08.01 P3377 左偏树模板 P3377 [模板]左偏树(可并堆) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<iostream> ...