C++ 单例模式的几种实现

 

 

懒汉模式是在第一次使用单例对象时才完成初始化工作。因为此时可能存在多线程竞态环境,如不加锁限制会导致重复构造或构造不完全问题。

饿汉模式则是利用外部变量,在进入程序入口函数之前就完成单例对象的初始化工作,此时是单线程所以不会存在多线程的竞态环境,故而无需加锁。 

 一、 懒汉模式,标准的 ”双检锁“  + ”自动回收“ 实现

class Singleton
{
public:
    static Singleton* GetInstance()
    {
        if (m_pInstance == NULL )
        {
            Lock(); // 加锁
            if (m_pInstance == NULL )
            {
                m_pInstance = new Singleton ();
            }
            UnLock(); // 解锁
        }
        return m_pInstance;
    }

    // 实现一个内嵌垃圾回收类    
    class CGarbo 
    {
    public:
        ~CGarbo()
        {
            if(Singleton::m_pInstance) 
                delete Singleton::m_pInstance;
        }
    };

    static CGarbo Garbo; // 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象

private:
    Singleton(){};
    Singleton(Singleton const&); 
    Singleton& operator=(Singleton const&); 

    static Singleton* m_pInstance;
};

Singleton* Singleton::m_pInstance = NULL;
Singleton::CGarbo Garbo;

二、懒汉模式,用静态局部变量 ,而不是new在堆上创建对象,避免自己回收资源。

class Singleton
{
public:
    static Singleton* GetInstance()
    {
        Lock(); // not needed after C++0x 
        static Singleton instance;  
        UnLock(); // not needed after C++0x 

        return &instance;
    }

private:
    Singleton() {};
    Singleton(const Singleton &);
    Singleton & operator = (const Singleton &);
};

在懒汉模式里,如果大量并发线程获取单例对象,在进行频繁加锁解锁操作时,必然导致效率低下。

 

三、饿汉模式,基础版本

因为程序一开始就完成了单例对象的初始化,所以后续不再需要考虑多线程安全性问题,就可以避免懒汉模式里频繁加锁解锁带来的开销。

class Singleton
{
public:

    static Singleton* GetInstance()
    {
        return &m_instance;
    }

private:
    Singleton(){};
    Singleton(Singleton const&); 
    Singleton& operator=(Singleton const&); 

    static Singleton m_instance;
};

Singleton Singleton::m_instance;  // 在程序入口之前就完成单例对象的初始化

虽然这种实现在一定程度下能良好工作,但是在某些情况下会带来问题 --- 就是在C++中 ”非局部静态对象“ 的 ”初始化“ 顺序 的 ”不确定性“, 参见Effective c++ 条款47。

考虑: 如果有两个这样的单例类,将分别生成单例对象A, 单例对象B. 它们分别定义在不同的编译单元(cpp中), 而A的初始化依赖于B 【 即A的构造函数中要调用B::GetInstance() ,而此时B::m_instance 可能还未初始化,显然调用结果就是非法的 】, 所以说只有B在A之前完成初始化程序才能正确运行,而这种跨编译单元的初始化顺序编译器是无法保证的。 

四、饿汉模式,增强版本(boost实现)

在前面的方案中:饿汉模式中,使用到了类静态成员变量,但是遇到了初始化顺序的问题; 懒汉模式中,使用到了静态局部变量,但是存在着线程安全等问题。

boost 的实现方式是:单例对象作为静态局部变量,然后增加一个辅助类,并声明一个该辅助类的类静态成员变量,在该辅助类的构造函数中,初始化单例对象。以下为代码

class Singleton
{
public:
    static Singleton* GetInstance()
    {
        static Singleton instance;
        return &instance;
    }

protected:
    // 辅助代理类
    struct Object_Creator
    {
        Object_Creator()
        {
            Singleton::GetInstance();
        }
    };
    static Object_Creator _object_creator;

    Singleton() {}
    ~Singleton() {}
};

Singleton::Object_Creator Singleton::_object_creator;

首先,代理类这个外部变量初始化时,在其构造函数内部调用 Singleton::GetInstance();从而间接完成单例对象的初始化,这就通过该代理类实现了饿汉模式的特性。

其次,仍然考虑第三种模式的缺陷。 当A的初始化依赖于B, 【 即A的构造函数中要调用B::GetInstance() ,而此时B::m_instance 可能还未初始化,显然调用结果就是非法的 】 现在就变为【在A的构造函数中要调用B::GetInstance() ,如果B尚未初始化,就会引发B的初始化】,所以在不同编译单元内全局变量的初始化顺序不定的问题就随之解决。  

 

大神理解:

最后,关于使用懒汉还是饿汉模式,我的理解:

如果这个单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,也是一种资源浪费吧。 所以这种情况懒汉模式(延迟加载)更好。

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

参考:https://www.cnblogs.com/xzy1210/p/3849253.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土拨鼠不是老鼠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值