首页 > 其他分享 >单例模式(手撕代码)

单例模式(手撕代码)

时间:2023-09-20 19:33:53浏览次数:25  
标签:pInstance Singleton 函数 代码 nullptr 模式 static 单例

一、单例模式

单例模式是常见的一种软件设计模式,单例对象的类只能实例化一个对象。

该类负责创建对象,同时保证只能创建一个对象。并提供一个访问它的全局访问点,该实例被所有程序模块共享。

一般应用与工具类的实现或者消耗资源的场景。

特点:

  1. 类构造函数私有
  2. 持有自己类的引用
  3. 对外获取实例的静态方法

代码:

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;

class singleton
{
public:
    static singleton* Getinstance()
    {
    //双重检查锁。
    //加锁之前检查一次是否为空,加锁之后再检查一次。
        if (nullptr == m_instance)
        {
            m_mtx.lock();
            if (nullptr == m_instance)
            {
                m_instance = new singleton();
            }
            m_mtx.unlock();
        }
        return m_instance;
    }
    
    //实现一个内嵌垃圾回收类
    class CGarbo{
    public:
        ~CGarbo(){
            if (singleton::m_instance)
                delete singleton::m_instance;
        }
    };
    //定义一个静态成员变量,程序结束时,会自动调用它的析构函数从而释放单例对象。
    static CGarbo Garbo;
private:
//构造函数私有
    singleton(){};
//防拷贝
    singleton(singleton const&);
    singleton& operator=(singleton const&);

    static singleton* m_instance;//单例对象指针
    static mutex m_mtx;//互斥锁
};
singleton* singleton::m_instance = nullptr;
singleton::CGarbo Garbo;
mutex singleton::m_mtx;

void func(int n)
{
    cout << singleton::Getinstance() << endl;
}

int mian()
{
    thread t1(func, 10);
    thread t2(func, 10);

    t1.join();
    t2.join();

    cout << singleton::Getinstance() << endl;
    cout << singleton::Getinstance() << endl;
}
  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象

二、C++ 类的默认八种函数

在C++中,一个类有八个默认函数:

1、默认构造函数;

2、默认拷贝构造函数;

3、默认析构函数;

4、默认重载赋值运算符函数;

5、默认重载取址运算符函数;

6、默认重载取址运算符const函数;

7、默认移动构造函数(C++11);

8、默认重载移动赋值操作符函数(C++11)。

在 C++ 中,如果我们定义了一个空类

编译器会自动为这个类生成构造函数、析构函数等。具体来说,我们定义了以上的一个 Dog 类,编译器会将它补全成一个如下自带 6 个函数的类:

class Dog {
// C++03
  Dog(); // 默认构造函数
  Dog(const Dog&); // 拷贝构造函数
  ~Dog() // 析构函数
  // 拷贝赋值运算符
  Dog& operator=(const Dog&);

// C++11 新增
  Dog(Dog&&); // 移动构造函数
  // 移动赋值运算符
  Dog& operator=(Dog&&);
};

一般的书上好像都是前面四种:默认构造函数,拷贝构造函数,默认赋值函数以及析构函数,

Dog*operator&(); // 取址运算符
const Dog*operator&() const; // 取址运算符const

这两种其实属于重载运算符,但要需要注意的是,只有当你需要用到这些函数的时候,编译器才会去定义它们。

三、论单例模式内存释放

(一)绝对不要在析构函数中释放单例

 我想说, 单例伴随着进程的生命周期, 常驻内存, 不需要程序员来释放(实际上, 人为释放是有风险的)。 如果进程终结, 对应的堆内存自动被回收, 不会泄露。

#include <iostream>
using namespace std;

class A
{
private:
    static A *m_p;
public:
    static A *getSingleTon()
    {
        if (NULL == m_p)
        {
            m_p = new A();
        }

        return m_p;
    }
    ~A()
    {
        if (NULL != m_p)
        {
            delete m_p;
            m_p = NULL;
        }
    }
};

A *A::m_p = NULL;

int main()
{
    return 0;
}

大家可以看看上述程序有什么问题。

如果没有看出问题, 那你再运行一下如下程序:

#include <iostream>
using namespace std;

class A
{
private:
    static A* m_p;
public:
    static A* getSingleTon()
    {
        if (NULL == m_p)
        {
            m_p = new A();
        }
        return m_p;
    }

    ~A()
    {
        if (NULL != m_p)
        {
            cout << "xxx" << endl;
            delete m_p; // 递归调用析构
            m_p = NULL;
            cout << "yyy" << endl; // 永远也不会执行
        }
    }
};

A* A::m_p = NULL;

int main()
{
    A* p = A::getSingleTon();
    delete p;

    return 0;
}

输出:

xxx
xxx
........

运行发现, 析构函数被多次调用了, 为什么呢?当类的使用者调用delete p;的时候, 实际上就是调用析构函数来释放单例, 但是, 现在类的提供者在析构函数中又delete这个单例, 显然又会调用析构函数, 所以形成了递归调用析构函数, 系统不异常才怪呢。

我们来反思一下, 为什么会出上述问题呢?肯定是写SingleTon的人牢牢记住了: 要在析构函数中释放资源。  但是, 他不明白, 单例应该由类的使用者来释放, 而不是类的提供者。 不要把角色搞错了。

千万不要说, 为什么出这么低级的问题! 其实, 这个问题不低级, 是个比较隐蔽的错误。 而且, 当代码多了(比如100w行), 离职的人多了, 经几次交接后, 那代码就可想而知了哭

下面, 我们继续看看:

#include <iostream>
using namespace std;

class A
{
private:
    static A* m_p;
    int x;
    A()
    {
        x = 1;
    }

public:
    static A* getSingleTon()
    {
        if (NULL == m_p)
        {
            m_p = new A();
        }
        return m_p;
    }

    ~A()
    {
        if (NULL != m_p)
        {
            cout << "xxx:" << x << endl; // 永远是xxx1
            delete m_p; // 递归调用析构
            m_p = NULL;
            cout << "yyy:" << x << endl; // 永远也不会执行, 单例也不会被释放
        }
    }
};

A* A::m_p = NULL;

int main()
{
    A* p = A::getSingleTon();
    delete p;

    return 0;
}

输出:

xxx:1
xxx:1

......

 从结果看, x的值一直是1, 所以单例根本就没有析构掉, 也就是说, 没有执行析构函数右边的花括号处, 单例就不会被释放。 

实际上, 要快速定位到析构函数的问题, 还是很不容易的, 那么多代码, 进程死掉, 咋快速定位?尤其是, 如果析构函数中没有日志打印, 根本就很难知道析构函数被多次执行了。 所以, 关于日志, 我强烈建议:

       1. 所有的构造函数和析构函数都必须有日志打印。

       2. 不被频繁调用的函数中, 必须有日志(很多人只喜欢在某些异常分支打日志, 甚至连异常分支都不打印日志, 确实太流氓了)

(二)单例模式的自动释放

1.借助友元类

//利用友元类,实现单例模式的自动释放

#include <stdio.h>
#include <iostream>

using std::cout;
using std::endl;
using std::cin;

class AutoRelease;

class Singleton{
    //单例模式的类
public:
    static Singleton *getInstance();//返回单例指针

private:
    friend class AutoRelease;
    Singleton();  //构造函数和析构函数都得是private
    ~Singleton();
    static Singleton *_pInstance;
};

Singleton *Singleton::getInstance(){
    if(_pInstance == nullptr){
        _pInstance = new Singleton();
    }
    return _pInstance;
}

Singleton::Singleton()
{
    cout << "Singleton()" << endl;
}

Singleton::~Singleton(){
    cout << "~Singleton()" << endl;
}

class AutoRelease{
    //用来实现单例的自动释放的类
    //应该保存在栈上,程序结束时自动回收单例的资源
public:
    AutoRelease(){
        cout << "AutoRelease()" << endl;
    }
    ~AutoRelease(){
        cout << "~AutoRelease()" << endl;
        if(Singleton::_pInstance == nullptr){
            return;
        }
        delete Singleton::_pInstance;
        Singleton::_pInstance = nullptr;
    }
};

Singleton *Singleton::_pInstance = nullptr;  //饱汉模式

int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    AutoRelease at;
    printf("s1 = %p\n", s1);
    printf("s2 = %p\n", s2);
    s1 = nullptr;
    s2 = nullptr;
    return 0;
}

2.借助内部类和静态数据成员

//利用内部类,实现单例模式的自动释放

#include <stdio.h>
#include <iostream>

using std::cout;
using std::endl;
using std::cin;

class Singleton{
    //单例模式的类
public:
    static Singleton *getInstance();//返回单例指针

private:
    friend class AutoRelease;
    Singleton();  //构造函数和析构函数都得是private
    ~Singleton();
    static Singleton *_pInstance;

private:
    //应该设计为私有类,避免类外的其他成员使用
    class AutoRelease{
        //用来实现单例的自动释放的内部类
        //应该保存在栈上,程序结束时自动回收单例的资源
    public:
        AutoRelease(){
            cout << "AutoRelease()" << endl;
        }
        ~AutoRelease(){
            cout << "~AutoRelease()" << endl;
            if(Singleton::_pInstance == nullptr){
                return;
            }
            delete Singleton::_pInstance;
            Singleton::_pInstance = nullptr;
        }
    };

private:
    static AutoRelease _at;  //由于AutoRelease是private,所以对象应该放在静态区
};

Singleton *Singleton::getInstance(){
    if(_pInstance == nullptr){
        _pInstance = new Singleton();
    }
    return _pInstance;
}

Singleton::Singleton()
{
    cout << "Singleton()" << endl;
}

Singleton::~Singleton(){
    cout << "~Singleton()" << endl;
}


/* Singleton *Singleton::_pInstance = nullptr;  //饱汉模式 */
//饱汉模式多线程时不安全,需要使用饿汉模式,在程序跑起来前就生成单例对象
Singleton *Singleton::_pInstance = Singleton::getInstance();//饿汉模式
Singleton::AutoRelease Singleton::_at;

int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    printf("s1 = %p\n", s1);
    printf("s2 = %p\n", s2);
    s1 = nullptr;
    s2 = nullptr;
    return 0;
}

3.借助atexit()函数

//利用atexit函数,实现单例模式的自动释放

#include <stdio.h>
#include <iostream>

using std::cout;
using std::endl;
using std::cin;

class Singleton{
    //单例模式的类
public:
    static Singleton *getInstance();//返回单例指针
    static void destroy();

private:
    friend class AutoRelease;
    Singleton();  //构造函数和析构函数都得是private
    ~Singleton();
    static Singleton *_pInstance;
};

Singleton *Singleton::getInstance(){
    if(_pInstance == nullptr){
        _pInstance = new Singleton();
        //注册destroy函数,在进程结束的时候执行,从而自动回收单例
        atexit(Singleton::destroy);
    }
    return _pInstance;
}

void Singleton::destroy(){
    if(Singleton::_pInstance == nullptr){
        return;
    }
    delete Singleton::_pInstance;
    Singleton::_pInstance = nullptr;
}

Singleton::Singleton()
{
    cout << "Singleton()" << endl;
}

Singleton::~Singleton(){
    cout << "~Singleton()" << endl;
}

//为了保证多线程情况下的安全性,使用饿汉模式
Singleton *Singleton::_pInstance = Singleton::getInstance();  //饿汉模式

int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    printf("s1 = %p\n", s1);
    printf("s2 = %p\n", s2);
    s1 = nullptr;
    s2 = nullptr;
    return 0;
}

4.借助pthread_once和atexit函数

//利用pthread_once和atexit函数,实现单例模式的自动释放

#include <stdio.h>
#include <iostream>

using std::cout;
using std::endl;
using std::cin;

class Singleton{
    //单例模式的类
public:
    static void init();
    static Singleton *getInstance();//返回单例指针
    static void destroy();

private:
    friend class AutoRelease;
    Singleton();  //构造函数和析构函数都得是private
    ~Singleton();
    static pthread_once_t _once;
    static Singleton *_pInstance;
};

void Singleton::init(){
    //初始化单例,注册回收函数
    if(_pInstance == nullptr){
        _pInstance = new Singleton();
        atexit(Singleton::destroy);
    }
}

Singleton *Singleton::getInstance(){
    //执行pthread_once,保证在多线程的情况下创建单例对象的安全性
    pthread_once(&_once, init);

    return _pInstance;
}

void Singleton::destroy(){
    if(Singleton::_pInstance == nullptr){
        return;
    }
    delete Singleton::_pInstance;
    Singleton::_pInstance = nullptr;
}

Singleton::Singleton()
{
    cout << "Singleton()" << endl;
}

Singleton::~Singleton(){
    cout << "~Singleton()" << endl;
}

//由于已经使用了pthread_once来保证安全性,所以使用饱汉模式即可
Singleton *Singleton::_pInstance = nullptr;
/* Singleton *Singleton::_pInstance = Singleton::getInstance();  //饿汉模式 */
pthread_once_t Singleton::_once = PTHREAD_ONCE_INIT;

int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    printf("s1 = %p\n", s1);
    printf("s2 = %p\n", s2);
    s1 = nullptr;
    s2 = nullptr;
    return 0;
}

 

标签:pInstance,Singleton,函数,代码,nullptr,模式,static,单例
From: https://www.cnblogs.com/imreW/p/17718182.html

相关文章

  • 《R语言实战.第2版》PDF高质量正版电子书含源代码
    本书注重实用性,是一本全面而细致的R指南,高度概括了该软件和它的强大功能,展示了使用的统计示例,且对于难以用传统方法处理的凌乱、不完整和非正态的数据给出了优雅的处理方法。作者不仅仅探讨统计分析,还阐述了大量探索和展示数据的图形功能。新版做了大量更新和修正,新增了近200页内容......
  • 《Linux命令行与shell脚本编程大全.第3版》电子书PDF+源代码
    精通Linux命令行与shell脚本编程,尽在本书中本书是关于Linux命令行和shell命令的全面参考资料,涵盖详尽的动手教程和实际应用指南,并提供相关参考信息和背景资料,带你从Linux命令行基础入手,直到写出自己的shell。时隔四年后的这一版本,针对Linux的新特性和实践,进行了全面更新:使用......
  • 《同构JavaScript应用开发》电子书PDF+源代码
    本书将向你展示如何构建和维护属于自己的同构JavaScript应用。全书分为三部分,第一部分描绘不同种类的同构JavaScript的轮廓,第二部分介绍关键概念,第三部分提供业界同行的解决方案案例。通过阅读本书,你将了解到这种应用架构日益流行的原因,并将其运用于解决关键的业务问题,如页面加载速......
  • 代码混淆工具ipaguard:如何使用ipaguard保护和混淆iOS应用程序代码
    ​转载:怎么保护苹果手机移动应用程序iosipa文件中的代码? 目录转载:怎么保护苹果手机移动应用程序iosipa文件中的代码?代码混淆步骤1.选择要混淆保护的ipa文件2.选择要混淆的类名称3.选择要混淆保护的函数,方法4.配置签名证书5.混淆和测试运行   ​编辑在......
  • 设计模式之单例模式
    单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Java中,实现单例模式有多种方式,以下是其中两种常见的实现方式:饿汉式单例模式(EagerInitialization):在类加载时就创建实例,并且该实例在整个程序生命周期内都是唯一的。publicclassSingleton{......
  • 用低代码开发工具高效构建企业门户
    企业信息门户EIP是指将各种应用系统、数据资源和互联网资源统一集中,根据每个用户使用特点和角色的不同,形成个性化的应用界面,并通过对事件和消息的处理、传输把用户有机地联系在一起。企业随着业务的发展,运作的复杂度也在不断加大,再加上各部门的业务量和业务的复杂度都在不断增加,可......
  • FPGA 让LED灯按照指定的亮灭模式亮灭,亮灭模式未知,由用户随机指定
    代码内容如下:modulecounter_led_3(Clk,Reset_n,Ctrl_n,Led);inputClk;inputReset_n;input[7:0]Ctrl_n;outputregLed;reg[26:0]counter;parameterMCNT=100000000;always@(posedgeClkornegedgeRe......
  • 一键实现冒泡排序算法,代码质量有保障!
    近年来,深度学习和神经语言模型作为提高开发人员生产力的手段,尤其是2022年11月30日,ChatGPT这一现象级热点得出横空出世,在全球范围内形成了热烈的讨论,其中关于自动化代码生成和其它软件工程方面受到了极大的关注。 软件开发过程涵盖了各种代码生成任务,包括代码自动生成、代码翻......
  • HNU_个人项目_中小学数学卷子自动生成程序_简要分析何梁雨代码
    一、前言感谢老师安排的这一次互评,以及我的结对编程伙伴何梁雨。在互评中我学到了不一样的编程思路,更清晰的感受到了自己编程水平哪一部分存在缺陷,并向这个方向学习改正。二、测试与评价1.测试程序运行(1)界面整洁简单,流程清晰。动作转折的地方经常会有一长串横杠隔开,让......
  • CodeArts Check代码检查服务用户声音反馈集锦(5)
    作者:gentle_zhou原文链接:<https://bbs.huaweicloud.com/blogs/401608>CodeArtsCheck(原CodeCheck),是自主研发的代码检查服务。建立在华为30年自动化源代码静态检查技术积累与企业级应用经验的沉淀之上,为用户提供代码风格、通用质量与网络安全风险等丰富的检查能力,提供全面质量报告......