C++单例实现

单例本来是个很简单的模式,实现上应该也是很简单,但C++单例的简单实现会有一些坑,来看看为了避免这些坑怎样一步步演化到boost库的实现方式。

方案一

class QMManager
{
public:
    static QMManager &instance()
    {
        static QMManager instance_;
        return instance_;
    }
}
这是最简单的版本,在单线程下(或者是C++0X下)是没任何问题的,但在多线程下就不行了,因为static QMManager instance_;这句话不是线程安全的。
在局部作用域下的静态变量在编译时,编译器会创建一个附加变量标识静态变量是否被初始化,会被编译器变成像下面这样(伪代码):

static QMManager &instance()
{
    static bool constructed = false;
    static uninitialized QMManager instance_;
    if (!constructed) {
        constructed = true;
        new(&s) QMManager; //construct it
    }
    return instance_;
}

这里有竞争条件,两个线程同时调用instance()时,一个线程运行到if语句进入后还没设constructed值,此时切换到另一线程,constructed值还是false,同样进入到if语句里初始化变量,两个线程都执行了这个单例类的初始化,就不再是单例了。

方案二

一个解决方法是加锁:

static QMManager &instance()
{
    Lock(); //锁自己实现
    static QMManager instance_;
    UnLock();
    return instance_;
}

但这样每次调用instance()都要加锁解锁,代价略大。

方案三

那再改变一下,把内部静态实例变成类的静态成员,在外部初始化,也就是在include了文件,main函数执行前就初始化这个实例,就不会有线程重入问题了:

class QMManager
{
protected:
    static QMManager instance_;
    QMManager();
    ~QMManager(){};
public:
    static QMManager *instance()
    {
        return &instance_;
    }
    void do_something();
};
QMManager QMManager::instance_; //外部初始化

这被称为饿汉模式,程序一加载就初始化,不管有没有调用到。

看似没问题,但还是有坑,在一个2B情况下会有问题:在这个单例类的构造函数里调用另一个单例类的方法可能会有问题。

看例子:

//.h
class QMManager
{
protected:
    static QMManager instance_;
    QMManager();
    ~QMManager(){};
public:
    static QMManager *instance()
    {
        return &instance_;
    }
};
 
class QMSqlite
{
protected:
    static QMSqlite instance_;
    QMSqlite();
    ~QMSqlite(){};
public:
    static QMSqlite *instance()
    {
        return &instance_;
    }
    void do_something();
};
 
QMManager QMManager::instance_;
QMSqlite QMSqlite::instance_;
//.cpp
QMManager::QMManager()
{
    printf("QMManager constructor\n");
    QMSqlite::instance()->do_something();
}
 
QMSqlite::QMSqlite()
{
    printf("QMSqlite constructor\n");
}
void QMSqlite::do_something()
{
    printf("QMSqlite do_something\n");
}

这里QMManager的构造函数调用了QMSqlite的instance函数,但此时QMSqlite::instance_可能还没有初始化。

这里的执行流程:程序开始后,在执行main前,执行到QMManager QMManager::instance_;这句代码,初始化QMManager里的instance_静态变量,调用到QMManager的构造函数,在构造函数里调用QMSqlite::instance(),取QMSqlite里的instance_静态变量,但此时QMSqlite::instance_还没初始化,问题就出现了。

那这里会crash吗,测试结果是不会,这应该跟编译器有关,静态数据区空间应该是先被分配了,在调用QMManager构造函数前,QMSqlite成员函数在内存里已经存在了,只是还未调到它的构造函数,所以输出是这样:

QMManager constructor
QMSqlite do_something
QMSqlite constructor

方案四

那这个问题怎么解决呢,单例对象作为静态局部变量有线程安全问题,作为类静态全局变量在一开始初始化,有以上2B问题,那结合下上述两种方式,可以解决这两个问题。boost的实现方式是:单例对象作为静态局部变量,但增加一个辅助类让单例对象可以在一开始就初始化。如下:

//.h
class QMManager
{
protected:
    struct object_creator
    {
        object_creator()
        {
            QMManager::instance();
        }
        inline void do_nothing() const {}
    };
    static object_creator create_object_;
 
    QMManager();
    ~QMManager(){};
public:
    static QMManager *instance()
    {
        static QMManager instance;
        return &instance;
    }
};
QMManager::object_creator QMManager::create_object_;
 
class QMSqlite
{
protected:
    QMSqlite();
    ~QMSqlite(){};
    struct object_creator
    {
        object_creator()
        {
            QMSqlite::instance();
        }
        inline void do_nothing() const {}
    };
    static object_creator create_object_;
public:
    static QMSqlite *instance()
    {
        static QMSqlite instance;
        return &instance;
    }
    void do_something();
};
 
QMManager::object_creator QMManager::create_object_;
QMSqlite::object_creator QMSqlite::create_object_;

结合方案3的.cpp,这下可以看到正确的输出和调用了:

QMManager constructor
QMSqlite constructor
QMSqlite do_something

来看看这里的执行流程:

初始化QMManager类全局静态变量create_object_
->调用object_creator的构造函数
->调用QMManager::instance()方法初始化单例
->执行QMManager的构造函数
->调用QMSqlite::instance()
->初始化局部静态变量QMSqlite instance
->执行QMSqlite的构造函数,然后返回这个单例。

跟方案三的区别在于QMManager调用QMSqlite单例时,方案3是取到全局静态变量,此时这个变量未初始化,而方案四的单例是静态局部变量,此时调用会初始化。

跟最初方案一的区别是在main函数前就初始化了单例,不会有线程安全问题。

最终boost

上面为了说明清楚点去除了模版,实际使用是用模版,不用写那么多重复代码,这是boost库的模板实现:

template <typename T>
struct Singleton
{
    struct object_creator
    {
        object_creator(){ Singleton<T>::instance(); }
        inline void do_nothing()const {}
    };
 
    static object_creator create_object;
 
public:
    typedef T object_type;
    static object_type& instance()
    {
        static object_type obj;
        //据说这个do_nothing是确保create_object构造函数被调用
        //这跟模板的编译有关
        create_object.do_nothing();
        return obj;
    }
 
};
template <typename T> typename Singleton<T>::object_creator Singleton<T>::create_object;
 
class QMManager
{
protected:
    QMManager();
    ~QMManager(){};
    friend class Singleton<QMManager>;
public:
    void do_something(){};
};
 
int main()
{
    Singleton<QMManager>::instance()->do_something();
    return 0;
}

其实Boost库这样的实现像打了几个补丁,用了一些奇技淫巧,虽然确实绕过了坑实现了需求,但感觉挺不好的。

参考资料:

http://blog.csdn.net/fullsail/article/details/8483106

http://blog.cnbang.net/tech/2229/

以上是为实现单例在多线程运行中正常运行而做的修改。

下面介绍一下基于boost库实现单例的简单使用方法。

为了让单例使用起来更为方便,可定义如下宏:

#define SINGLE(classname) Singleton<classname>::instance()

使用时,可直接调用:

SINGLE(MyClass)->...

总结:

本文介绍了单例的实现方法,并介绍了简单的单例封装方法,下一篇将介绍单例的管理。



版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://www.cnblogs.com/wangicter/p/4767197.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值