C++单例模式的几种实现方法详解

目录
  • 局部静态变量方式
  • 静态成员变量指针方式
  • 智能指针方式
  • 辅助类智能指针单例模式
  • 通用的单例模板类
  • 总结

局部静态变量方式

//通过静态成员变量实现单例
//懒汉式
class Single2
{
private:
    Single2()
    {
    }
    Single2(const Single2 &) = delete;
    Single2 &operator=(const Single2 &) = delete;
public:
    static Single2 &GetInst()
    {
        static Single2 single;
        return single;
    }
};

上述代码通过局部静态成员single实现单例类,原理就是函数的局部静态变量生命周期随着进程结束而结束。上述代码通过懒汉式的方式实现。

调用如下

void test_single2()
{
    //多线程情况下可能存在问题
    cout << "s1 addr is " << &Single2::GetInst() << endl;
    cout << "s2 addr is " << &Single2::GetInst() << endl;
}

程序输出如下

sp1  is  0x1304b10
sp2  is  0x1304b10

确实生成了唯一实例,上述单例模式存在隐患,对于多线程方式生成的实例可能时多个。

静态成员变量指针方式

可以定义一个类的静态成员变量,用来控制实现单例

//饿汉式
class Single2Hungry
{
private:
    Single2Hungry()
    {
    }
    Single2Hungry(const Single2Hungry &) = delete;
    Single2Hungry &operator=(const Single2Hungry &) = delete;
public:
    static Single2Hungry *GetInst()
    {
        if (single == nullptr)
        {
            single = new Single2Hungry();
        }
        return single;
    }
private:
    static Single2Hungry *single;
};

这么做的一个好处是我们可以通过饿汉式的方式避免线程安全问题

//饿汉式初始化
Single2Hungry *Single2Hungry::single = Single2Hungry::GetInst();
void thread_func_s2(int i)
{
    cout << "this is thread " << i << endl;
    cout << "inst is " << Single2Hungry::GetInst() << endl;
}
void test_single2hungry()
{
    cout << "s1 addr is " << Single2Hungry::GetInst() << endl;
    cout << "s2 addr is " << Single2Hungry::GetInst() << endl;
    for (int i = 0; i < 3; i++)
    {
        thread tid(thread_func_s2, i);
        tid.join();
    }
}
int main(){
    test_single2hungry()
}

程序输出如下

s1 addr is 0x1e4b00
s2 addr is 0x1e4b00
this is thread 0
inst is 0x1e4b00
this is thread 1
inst is 0x1e4b00
this is thread 2
inst is 0x1e4b00

可见无论单线程还是多线程模式下,通过静态成员变量的指针实现的单例类都是唯一的。饿汉式是在程序启动时就进行单例的初始化,这种方式也可以通过懒汉式调用,无论饿汉式还是懒汉式都存在一个问题,就是什么时候释放内存?多线程情况下,释放内存就很难了,还有二次释放内存的风险。

我们定义一个单例类并用懒汉式方式调用

//懒汉式指针
//即使创建指针类型也存在问题
class SinglePointer
{
private:
    SinglePointer()
    {
    }
    SinglePointer(const SinglePointer &) = delete;
    SinglePointer &operator=(const SinglePointer &) = delete;
public:
    static SinglePointer *GetInst()
    {
        if (single != nullptr)
        {
            return single;
        }
        s_mutex.lock();
        if (single != nullptr)
        {
            s_mutex.unlock();
            return single;
        }
        single = new SinglePointer();
        s_mutex.unlock();
        return single;
    }
private:
    static SinglePointer *single;
    static mutex s_mutex;
};

在cpp文件里初始化静态成员,并定义一个测试函数

//懒汉式
//在类的cpp文件定义static变量
SinglePointer *SinglePointer::single = nullptr;
std::mutex SinglePointer::s_mutex;
void thread_func_lazy(int i)
{
    cout << "this is lazy thread " << i << endl;
    cout << "inst is " << SinglePointer::GetInst() << endl;
}
void test_singlelazy()
{
    for (int i = 0; i < 3; i++)
    {
        thread tid(thread_func_lazy, i);
        tid.join();
    }
    //何时释放new的对象?造成内存泄漏
}
int main(){
    test_singlelazy();
}

函数输出如下

this is lazy thread 0
inst is 0xbc1700
this is lazy thread 1
inst is 0xbc1700
this is lazy thread 2
inst is 0xbc1700

此时生成的单例对象的内存空间还没回收,这是个问题,另外如果多线程情况下多次delete也会造成崩溃。

智能指针方式

可以利用智能指针自动回收内存的机制设计单例类

//利用智能指针解决释放问题
class SingleAuto
{
private:
    SingleAuto()
    {
    }
    SingleAuto(const SingleAuto &) = delete;
    SingleAuto &operator=(const SingleAuto &) = delete;
public:
    ~SingleAuto()
    {
        cout << "single auto delete success " << endl;
    }
    static std::shared_ptr<SingleAuto> GetInst()
    {
        if (single != nullptr)
        {
            return single;
        }
        s_mutex.lock();
        if (single != nullptr)
        {
            s_mutex.unlock();
            return single;
        }
        single = std::shared_ptr<SingleAuto>(new SingleAuto);
        s_mutex.unlock();
        return single;
    }
private:
    static std::shared_ptr<SingleAuto> single;
    static mutex s_mutex;
};

SingleAuto的GetInst返回std::shared_ptr类型的变量single。因为single是静态成员变量,所以会在进程结束时被回收。智能指针被回收时会调用内置指针类型的析构函数,从而完成内存的回收。

在主函数调用如下测试函数

// 智能指针方式
std::shared_ptr<SingleAuto> SingleAuto::single = nullptr;
mutex SingleAuto::s_mutex;
void test_singleauto()
{
    auto sp1 = SingleAuto::GetInst();
    auto sp2 = SingleAuto::GetInst();
    cout << "sp1  is  " << sp1 << endl;
    cout << "sp2  is  " << sp2 << endl;
    //此时存在隐患,可以手动删除裸指针,造成崩溃
    // delete sp1.get();
}
int main(){
    test_singleauto();
}

程序输出如下

sp1  is  0x1174f30
sp2  is  0x1174f30

智能指针方式不存在内存泄漏,但是有一个隐患就是单例类的析构函数时public的,如果被人手动调用会存在崩溃问题,比如将上边test_singleauto中的注释打开,程序会崩溃。

辅助类智能指针单例模式

智能指针在构造的时候可以指定删除器,所以可以传递一个辅助类或者辅助函数帮助智能指针回收内存时调用我们指定的析构函数。

// safe deletor
//防止外界delete
//声明辅助类
//该类定义仿函数调用SingleAutoSafe析构函数
//不可以提前声明SafeDeletor,编译时会提示incomplete type
// class SafeDeletor;
//所以要提前定义辅助类
class SingleAutoSafe;
class SafeDeletor
{
public:
    void operator()(SingleAutoSafe *sf)
    {
        cout << "this is safe deleter operator()" << endl;
        delete sf;
    }
};
class SingleAutoSafe
{
private:
    SingleAutoSafe() {}
    ~SingleAutoSafe()
    {
        cout << "this is single auto safe deletor" << endl;
    }
    SingleAutoSafe(const SingleAutoSafe &) = delete;
    SingleAutoSafe &operator=(const SingleAutoSafe &) = delete;
    //定义友元类,通过友元类调用该类析构函数
    friend class SafeDeletor;
public:
    static std::shared_ptr<SingleAutoSafe> GetInst()
    {
        if (single != nullptr)
        {
            return single;
        }
        s_mutex.lock();
        if (single != nullptr)
        {
            s_mutex.unlock();
            return single;
        }
        //额外指定删除器
        single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDeletor());
        //也可以指定删除函数
        // single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDelFunc);
        s_mutex.unlock();
        return single;
    }
private:
    static std::shared_ptr<SingleAutoSafe> single;
    static mutex s_mutex;
};

SafeDeletor要写在SingleAutoSafe上边,并且SafeDeletor要声明为SingleAutoSafe类的友元类,这样就可以访问SingleAutoSafe的析构函数了。

我们在构造single时制定了SafeDeletor(),single在回收时,会调用SingleAutoSafe的仿函数,从而完成内存的销毁。

并且SingleAutoSafe的析构函数为私有的无法被外界手动调用了。

//智能指针初始化为nullptr
std::shared_ptr<SingleAutoSafe> SingleAutoSafe::single = nullptr;
mutex SingleAutoSafe::s_mutex;
void test_singleautosafe()
{
    auto sp1 = SingleAutoSafe::GetInst();
    auto sp2 = SingleAutoSafe::GetInst();
    cout << "sp1  is  " << sp1 << endl;
    cout << "sp2  is  " << sp2 << endl;
    //此时无法访问析构函数,非常安全
    // delete sp1.get();
}
int main(){
    test_singleautosafe();
}

程序输出如下

sp1  is  0x1264f30
sp2  is  0x1264f30

通过辅助类调用单例类的析构函数保证了内存释放的安全性和唯一性。这种方式时生产中常用的。如果将test_singleautosafe函数的注释打开,手动delete sp1.get()编译阶段就会报错,达到了代码安全的目的。因为析构被设置为私有函数了。

通用的单例模板类

我们可以通过声明单例的模板类,然后继承这个单例模板类的所有类就是单例类了。达到泛型编程提高效率的目的。

template <typename T>
class Single_T
{
protected:
    Single_T() = default;
    Single_T(const Single_T<T> &st) = delete;
    Single_T &operator=(const Single_T<T> &st) = delete;
    ~Single_T()
    {
        cout << "this is auto safe template destruct" << endl;
    }
public:
    static std::shared_ptr<T> GetInst()
    {
        if (single != nullptr)
        {
            return single;
        }
        s_mutex.lock();
        if (single != nullptr)
        {
            s_mutex.unlock();
            return single;
        }
        //额外指定删除器
        single = std::shared_ptr<T>(new T, SafeDeletor_T<T>());
        //也可以指定删除函数
        // single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDelFunc);
        s_mutex.unlock();
        return single;
    }
private:
    static std::shared_ptr<T> single;
    static mutex s_mutex;
};
//模板类的static成员要放在h文件里初始化
template <typename T>
std::shared_ptr<T> Single_T<T>::single = nullptr;
template <typename T>
mutex Single_T<T>::s_mutex;

我们定义一个网络的单例类,继承上述模板类即可,并将构造和析构设置为私有,同时设置友元保证自己的析构和构造可以被友元类调用.

//通过继承方式实现网络模块单例
class SingleNet : public Single_T<SingleNet>
{
private:
    SingleNet() = default;
    SingleNet(const SingleNet &) = delete;
    SingleNet &operator=(const SingleNet &) = delete;
    ~SingleNet() = default;
    friend class SafeDeletor_T<SingleNet>;
    friend class Single_T<SingleNet>;
};

在主函数中调用如下

void test_singlenet()
{
    auto sp1 = SingleNet::GetInst();
    auto sp2 = SingleNet::GetInst();
    cout << "sp1  is  " << sp1 << endl;
    cout << "sp2  is  " << sp2 << endl;
}

程序输出如下

sp1  is  0x1164f30
sp2  is  0x1164f30

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C++实现单例模式的方法

    目录 饿汉模式 懒汉模式 锁 + 智能指针 局部静态变量 总结 饿汉模式 类实例化就会占用内存,浪费资源,效率高,不存在线程安全问题. class Singleton{ Singleton() { } static Singleton* m_instance_ptr; public: static Singleton* get_instance() { return m_instance_ptr; } }; Singleton* Singleton::m_instance_ptr = new S

  • C++单例模式的懒汉模式和饿汉模式详解

    目录 懒汉模式 饿汉模式 线程安全的懒汉模式 总结 懒汉模式 懒汉模式在第一次用到类实例的时候才会去实例化,就是不到调用getInstance函数时,这个类的对象是一直不存在的.懒汉本身是线程不安全的. #include <iostream> using namespace std; class Singelton{ private: Singelton(){ m_count ++; printf("Singelton begin\n"); Sleep(1000);// 加

  • C++ 单例模式的几种实现方式研究

    单例模式 单例模式,可以说设计模式中最常应用的一种模式了,据说也是面试官最喜欢的题目.但是如果没有学过设计模式的人,可能不会想到要去应用单例模式,面对单例模式适用的情况,可能会优先考虑使用全局或者静态变量的方式,这样比较简单,也是没学过设计模式的人所能想到的最简单的方式了. 一般情况下,我们建立的一些类是属于工具性质的,基本不用存储太多的跟自身有关的数据,在这种情况下,每次都去new一个对象,即增加了开销,也使得代码更加臃肿.其实,我们只需要一个实例对象就可以.如果采用全局或者静态变量的方式,会

  • C++实现单例模式的自动释放

    单例模式是为了确保某个类只能创建一个对象而设计的.当一个程序的某个类型只允许有一个实例的时候使用. 一般采用动态分配的方式来生成单例对象,这个时候C++程序员就需要考虑内存回收的问题了,所以为了避免在使用单例模式时忘记回收资源而造成内存泄漏的问题,在实现单例模式的时候就使其可以自动被回收. 不带自动释放的单例模式的实现与销毁 我们先来复习一下没有自动回收机制的单例模式的实现和销毁. 单例模式的实现: 将构造函数私有化 在类中定义一个静态的指向本类型的指针变量 定义一个返回值为该类的指针的静态成员

  • C++ 单例模式的详解及实例

    C++ 单例模式的详解及实例 1.什么叫单例模式? 单例模式也称为单件模式.单子模式,可能是使用最广泛的设计模式.其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享.有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘. 通过单例模式, 可以做到: (1)确保一个类只有一个实例被建立 (2)提供了一个对对象的全局访问指针 (3)在不影响单例类的客户端

  • C++设计模式之单例模式详解

    目录 单例模式:就是只有一个实例. 单例模式又分为两种基本的情形:饿汉式和懒汉式 如下是懒汉式单例类 小结: 继续看单例模式 总结 单例模式:就是只有一个实例. singleton pattern单例模式:确保某一个类在程序运行中只能生成一个实例,并提供一个访问它的全局访问点.这个类称为单例类.如一个工程中,数据库访问对象只有一个,电脑的鼠标只能连接一个,操作系统只能有一个窗口管理器等,这时可以考虑使用单例模式. 众所周知,c++中,类对象被创建时,编译系统为对象分配内存空间,并自动调用构造函数

  • C++单例模式的实例详解

    单例模式概述 个人认为单例模式是设计模式中最为简单.最为常见.最容易实现,也是最应该熟悉和掌握的模式.且不说公司企业在招聘的时候为了考察员工对设计的了解和把握,考的最多的就是单例模式. 单例模式解决问题十分常见,我们怎样去创建一个唯一的变量(对象)?在基于对象的设计中我们可以通过创建一个全局变量(对象)来实现,在面向对象和面向过程结合的设计范式(如 C++中)中,我们也还是可以通过一个全局变量实现这一点.但是当我们遇到了纯粹的面向对象范式中,这一点可能就只能是通过单例模式来实现了,可能这也正是很

  • C++ 超详细深入分析单例模式

    目录 不能被拷贝的类 C++98 C++11 只能在堆上创建对象的类 只能在栈上创建对象的类 不能被继承的类 C++98 C++11 只能创建一个对象的类(单例模式) 设计模式 单例模式 饿汉模式 懒汉模式 不能被拷贝的类 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可. C++98 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可. class CopyBan { //... p

  • 详解C++实现线程安全的单例模式

    在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 正解: template <class T> class singleton { protected: singleton(){}; private: singleton(const singleton&){};//禁止拷贝 singleton& operator=(const singleton&){};//禁止赋值 static T* m

  • C++线程安全的单例模式讲解

    废话不多说,常用的代码积淀下来. 一.懒汉模式 即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例. 需要用锁,来保证其线程安全性:原因:多个线程可能进入判断是否已经存在实例的if语句,从而non thread safety. 使用double-check来保证thread safety.但是如果处理大量数据时,该锁才成为严重的性能瓶颈. 1.静态成员实例的懒汉模式: class Singleton { private: static Singleton* m_instanc

随机推荐