首页 > 系统相关 >C++ 单例模式以及内存管理

C++ 单例模式以及内存管理

时间:2024-01-13 13:12:13浏览次数:23  
标签:初始化 Singleton C++ instance static 内存 单例 构造函数

引用:
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根本就没有在任何位置被调用初始化,其析构函数也就无从谈及被调用。

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

标签:初始化,Singleton,C++,instance,static,内存,单例,构造函数
From: https://www.cnblogs.com/kazusarua/p/17962246

相关文章

  • c++ opencv直线检测
     #include<opencv2/opencv.hpp>#include<opencv2/highgui/highgui.hpp>#include<opencv2/imgproc/imgproc.hpp>usingnamespacecv;intmain(intargc,char**argv){//读取图像Matsrc=imread(argv[1],CV_LOAD_IMAGE_COLOR);if......
  • 【C++】OpenCV4-线条、矩形、圆形、椭圆等图形的绘制与填充、RNG随机函数的使用
    图形的绘制与填充://图形的绘制与填充Matcanvas=Mat::zeros(Size(512,512),CV_8UC3);namedWindow("canvas",WINDOW_AUTOSIZE);//相关绘制API演示//绘制直线line(canvas,Point(10,10),Point(400,400),Scalar(255,0,0),1,LINE_8);//绘制矩形Rectrect(150,1......
  • 刷题笔记——队列(C++)
    1696.跳跃游戏VI-力扣(LeetCode)给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。一开始你在下标 0 处。每一步,你最多可以往前跳 k 步,但你不能跳出数组的边界。也就是说,你可以从下标 i 跳到 [i+1,min(n-1,i+k)] 包含 两个端点的任意位置。你的目标是......
  • C/C++程序的内存开辟——《初学C语言第55天》
    //————C/C++程序的内存开辟C++程序内存分配的几个区域://intt=2;//staticintr=1;//voidtest()//{//  statice=1;//  intn=1;//  intarr[10]={1,2,3,4};//  charg[]="helloworld";//  char*p="abcd";//  int*a=(int*)malloc......
  • 【C语言进阶篇】动态内存分配的六个常见错误
    <br>(文章目录)前言  <fontcolor=green>......
  • 异构计算关键技术之内存管理与DMA(一)
    异构计算关键技术之内存管理与DMA(一)诞生伊始,计算机处理能力就处于高速发展中。及至最近十年,随着大数据、区块链、AI等新技术的持续火爆,人们为提升计算处理速度更是发展了多种不同的技术思路。大数据受惠于分布式集群技术,区块链带来了专用处理器(Application-SpecificIC,ASIC)的春......
  • java基础语法面向对象之单个对象内存图
    一:概述在面向对象的学习中,需要去了解对象的内存图,在这里以单个对象的内存图为例进行讲解。二:具体说明<1>代码publicclassStudent{Stringname;intage;publicvoidstudy(){System.out.println(name+"好好学习");......
  • C++采集亚马逊amazon产品数据教程
    最近亚马逊电商非常火爆,今天我将用C++语言写一个亚马逊商品数据的爬虫程序,只要是用来收集一些产品相关信息。例如产品自身特性以及产品所对应的销量,为了后期布局亚马逊做一些参考,提供数据支持,同时另外我也会用C语言同样写一篇相关的爬虫教程,方便大家借鉴。首先,这是一个非常复杂的项......
  • 开发内存检测脚本
    if实践:1.单分支if2.if分支的嵌套3.开发内存监控的脚本4.开发nginx,mysql服务监控脚本5.开发rsync起停脚本6.作业:nginx服务监控脚本 1.单分支if条件测试语句,改造为if判断语句,if结合条件测试 将上述改造为if脚本:脚本内容: ......
  • C++模板例子
    title:"C++模板例子"date:2023-11-02T01:05:25+08:00tags:["C++"]categories:[]draft:false#include<vector>#include<type_traits>usingnamespacestd;classAA{};classBB{};classTest{public:template<cl......