引用:

https://zhuanlan.zhihu.com/p/37469260

https://www.cnblogs.com/xiaolincoding/p/11437231.html

https://blog.csdn.net/unonoi/article/details/121138176

单例模式:一个类在全局范围内只有一个实例化的对象

核心:

  1. 构造函数是私有的,防止外界创建单例类的对象。
  2. 使用类内的私有静态指针变量指向类的唯一实例。
  3. 提供一个public静态方法获取该实例。

单例有两种实现方式,区别是创建实例的时间不同:

  1. 饿汉式(Eager Singleton):在程序运行时就初始化创建实例,直接调用即可。这种方式是天然线程安全的。坏处:提前占用系统资源!
  2. 懒汉式(Lazy Singleton):在首次调用get_instance()时创建实例化对象。需要考虑线程安全。

如果该类可以被继承,则构造函数应该protected修饰

饿汉式

可以看到,构造函数在main函数之前就已经执行,Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;中已经在编译期new instance完成构造。

class Singleton
{
private:
static Singleton instance;
private:
Singleton(); // 如果该类可以被继承,则构造函数应该protected修饰
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance() {
return instance;
}
} // initialize defaultly
Singleton* Singleton::instance = new (std::nothrow) Singleton

然而,饿汉式确实有对应的潜在危险:

no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。

也即,static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。

懒汉式

Step1:普通非线程安全

class Singleton
{
private:
static Singleton* instance;
private:
Singleton() {}; // 如果该类可以被继承,则构造函数应该protected修饰
~Singleton() {};
Singleton(const Singleton&); // 拷贝构造
Singleton& operator=(const Singleton&); // 赋值构造
public:
static Singleton* getInstance()
{
if (instance == NULL)
instance = new Singleton();
return instance;
}
};
Singleton* Singleton::instance = NULL;

对于以上代码,显然多个线程对于if(instance == NULL) 判断与修改是非原子的,可能出现冲突。

而在实际的实验中,对于线程不安全的情况(普通懒汉),的确观察到了多个构造函数,且内存地址不同的情况,说明多线程并发的race condition创建了多个对象,与单例不符。

Step2:线程安全

针对以上问题,一个容易想到的解决方案是加锁,保护共享的instance免受冲突。

这里使用双检锁(DCL: Double-Checked Locking Pattern)来实现。

双检锁的好处在于:由于只有首次初始化才上锁,因此最外层仅用于判断是否获取instance成功。在构造完成后就没有可能再次进入本层循环内部,避免了每一次访问getInstance()都需要加锁。

class Singleton
{
private:
static Singleton* instance;
static std::mutex m_Mutex; private:
Singleton() {}; // 如果该类可以被继承,则构造函数应该protected修饰
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton* getInstance()
{
if (instance == NULL)
{
std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
if (instance == NULL)
{
instance = new (std::nothrow) Singleton;
}
}
return instance;
}
}; //初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;

Step2.5:强化的线程安全

本篇文章中提出了一个问题,获取到的非空instance对象可能会出现部分构造问题。

因此,提出了使用atomic的原子量操作方案,个人认为依赖于内存模型,由于编译优化,现代CPU的指令流水线重排以及乱序执行,缓存等各种影响因素,不排除有此种可能性。

虽然个人认为这依赖于具体编译器的实现,并且几乎不会出现,但是理论上的确有可能出现这样的问题。

或许也可以加一个volatile关键字来限制一下,但仍然没有完全排除可能性。

代码如下:

atomic<Widget*> Widget::pInstance{ nullptr };
Widget* Widget::Instance() {
if (pInstance == nullptr) {
lock_guard<mutex> lock{ mutW };
if (pInstance == nullptr) {
pInstance = new Widget();
}
}
return pInstance;
}

Step3:优雅的线程安全

C++ 11规定了local static在多线程条件下的初始化行为(g++ 03中已经显式说明),要求编译器保证了内部静态变量的线程安全性。

参见: https://stackoverflow.com/questions/449436/singleton-instance-declared-as-static-variable-of-getinstance-method-is-it-thre

因此,最为优雅的单例写法(Meyers' Singleton):

class Singleton
{
private:
Singleton() { }; // 如果该类可以被继承,则构造函数应该protected修饰
~Singleton() { };
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance()
{
static Singleton instance; // 核心改进,首次访问才会被初始化
return instance;
}
};
Singleton* Singleton::instance = NULL;

可能的内存释放问题

手动释放:delete Singleton::get_instance();

自动释放:

引用: https://blog.csdn.net/linuxwuj/article/details/81187564

进程结束时,静态对象的生命周期随之结束,其析构函数会被调用来释放对象。因此,我们可以利用这一特性,在单例类中声明一个内嵌类,该类的析构函数专门用来释放new出来的单例对象,并声明一个该类类型的static对象

class Singleton {
public:
// ...
private:
// ...
static Singleton * instance;
class GarbageCollector {
public:
~GarbageCollector() {
if (Singleton::instance) {
delete Singleton::instance;
Singleton::instance = 0;
}
}
};
static GarbageCollector gc;
}; // 定义
Singleton::GarbargeCollector Singleton::gc;
// ...

注意static GarbageCollector gc;这一个位置,我们定义了一个内部的嵌套类GarbageCollector,并且使用static修饰。

此处需要注意:类的静态成员需要在类外部进行初始化,所以一定要在类的最外部初始化Singleton::GarbargeCollector Singleton::gc;

否则静态成员GarbageCollector gc根本就没有在任何位置被调用初始化,其析构函数也就无从谈及被调用。

此外,针对内存管理,智能指针也是一个值得考虑的方案,不如说尽量去使用智能指针。

C++ 单例模式以及内存管理的更多相关文章

  1. 【Cocos2d-x 3.x】内存管理机制与源码分析

    侯捷先生说过这么一句话 :  源码之前,了无秘密. 要了解Cocos2d-x的内存管理机制,就得阅读源码. 接触Cocos2d-x时, Cocos2d-x的最新版本已经到了3.2的时代,在学习Coco ...

  2. 【原创】android内存管理-内存泄漏原因

    转载请注明出处 http://www.cnblogs.com/weiwangnuanyang/p/5704596.html 先讲一下内存泄漏的概念:内存泄露是指无用对象持续占有内存,或者内存得不到及时 ...

  3. JVM内存管理------GC算法精解(五分钟教你终极算法---分代搜集算法)

    引言 何为终极算法? 其实就是现在的JVM采用的算法,并非真正的终极.说不定若干年以后,还会有新的终极算法,而且几乎是一定会有,因为LZ相信高人们的能力. 那么分代搜集算法是怎么处理GC的呢? 对象分 ...

  4. OC中的属性、方法及内存管理

    普通方法:关注(代表)对象可以”干什么”,过程中需要实例变量.-(void)show;输出 … 访问属性    属性:属性专门处理实例变量.(程序执行过程当中)    初始化方法:一创建对象(第一时间 ...

  5. Cocos2d-x 3.1 内存管理机制

    Cocos2d-x使用的内存管理方式是引用计数.引用计数是一种非常有效的机制.通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数.当对象添加一次引用时,计数器加1:而对象失去一次引用时.计数 ...

  6. Android 内存管理之优化建议

    OOM(OutOfMemory)转:http://hukai.me/android-performance-oom/ 前面我们提到过使用getMemoryClass()的方法可以得到Dalvik He ...

  7. MRC下单例模式的内存问题与ARC实现

    单例模式保证一个类只能拥有一个静态的实例,类负责创建与维护这个实例,并提供一个统一的静态(类方法)访问方式,并封锁了这个类外部的代码对这个类对象的创建. .h文件: #import <Found ...

  8. iOS面试题05-父子控制器、内存管理

    内存管理.父子控制器面试题 1.建立父子关系控制器有什么用 回答:1>监听屏幕选中 2>如果想拿到你当前的很小的一个控制器所在的导航控制器必须要跟外面比较大的控制器建立父子关系,才能一层一 ...

  9. Java内存管理-掌握虚拟机类加载器(五)

    勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇介绍虚拟机类加载机制,讲解了类加载机制中的三个阶段,分别是:加载.连接(验证.准 ...

  10. Java内存管理-掌握虚拟机类加载机制(四)

    勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇介绍了整个JVM运行时的区域,以及简单对比了JDK7和JDK8中JVM运行时区域 ...

随机推荐

  1. Linux复制安装 jdk 环境

    转载请注明出处: 最近在弄服务器环境,发现可以通过复制已安装 jdk 的服务器配置到新的服务器,并配置服务器环境变量配置文件就可以完成. 操作步骤如下: 1. 查看以安装jdk服务器的环境配置,并复制 ...

  2. .NET静态代码织入——肉夹馍(Rougamo)发布2.2

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  3. 【ThreadX-GUIX】Azure RTOS GUIX和Azure RTOS GUIX Studio概述

    Azure GUIX嵌入式GUI是Microsoft的高级工业级GUI解决方案,专门针对深度嵌入式,实时和IoT应用程序而设计.Microsoft还提供了名为Azure RTOS GUIX Studi ...

  4. java - 对象装载数据返回

    1. 创建 Phone 类 package class_object; public class Phone { String brand; String color; double price; v ...

  5. [转帖]tiup cluster scale-in

    https://docs.pingcap.com/zh/tidb/stable/tiup-component-cluster-scale-in tiup cluster scale-in 命令用于集群 ...

  6. [转帖]decimal,float和double的区别是什么?

    https://zhuanlan.zhihu.com/p/352503879 今天复习mysql理论知识,在看常用数据类型的时候发现float和decimal类型都是表示小数,就展开搜索学习了一下区别 ...

  7. [转帖]《Linux性能优化实战》笔记(22)—— 网络丢包问题分析

    所谓丢包,是指在网络数据的收发过程中,由于种种原因,数据包还没传输到应用程序中,就被丢弃了.这些被丢弃包的数量,除以总的传输包数,也就是我们常说的丢包率.丢包率是网络性能中最核心的指标之一.丢包通常会 ...

  8. [转帖]传输层安全协议真(TLS)的安全吗?

    https://zhuanlan.zhihu.com/p/305161227 随着数字通信,计算机网络,公钥密码体制等技术的迅速发展,安全网络通信已经成为了人们的日常需求.TLS 作为目前被广泛应用的 ...

  9. [转帖]公钥基础设施(PKI,Public Key Infrastructure)闲谈

    https://zhuanlan.zhihu.com/p/384436119 背景 在现实空间中,人类的活动范围和接触人的范围有限,人和人最初的信任是建立在小团体或部落内部.随着全球化进展,人类的活动 ...

  10. iptables 命令学习

    iptables 命令学习 摘要 Linux 早起版本使用netfilter进行数据包过滤. 最新的版本开始改用 ebpf的方式进行内核编程式的包过滤. netfilter 可以理解为内核态的一个处理 ...