单例模式(手撕代码)

一、单例模式

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

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

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

特点:

  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;
}

 

posted @ 2023-09-20 19:27  ImreW  阅读(52)  评论(0编辑  收藏  举报