C++中单例模式

最简单的单例实现只需要一个全局对象:

some_class the_instance;
some_class *get_instance() {
    return &the_instance;
}
这个实现简单到没什么可说,它在绝大多数情况下能正常工作,之所以说是“绝大多数”,主要原因是:
1、它不能保证正确处理依赖性,也就是说,如果你有其它全局对象在构造时依赖于这个对象,你不能保证它能正常工作。
2、构造的时间点其实是不确定的,C++标准只要求这个对象在第一次被使用前,它的构造函数会被调用,具体实现中的保证甚至还达不到这个强度,绝大部分的实现只能保证main之后的函数中使用这个对象时,构造函数会被调用。
3、这个实现可能会导致你实例化了根本没用到的单例,因为“用到”这个对象的行为不一定能被C++准确识别,很多实现只是简单的在main之前按定义的顺序调用所有全局对象的构造函数。
所以,既然你在用C++11,那我们就用C++11的新特性来实现这个功能。

在C++11中引入了一个新的东西叫call_once,配合once_flag,你可以保证一个函数只会被调用一次,有了这个东西,创建一个单例的程序可以简化成这样:

some_class *the_instance;
std::once_flag instance_created;
some_class *get_instance() {
    std::call_once(
        instance_created,
        [](){ the_instance=new some_class(...); }
    );
    return the_instance;
}

这段程序在多线程环境里也能正常工作,也能处理单例间的依赖,而且不用在意各种OS/编译器规定的各种坑爹初始化次序,任何编译器,只要它正确实现了C++11标准中的thread部分,就能保证这一点。

单例的销毁其实也是个挺烦问题,最主要的问题是你很难准确的知道你从什么时候开始才真得不再用它,一个简单的思路是atexit放一个析构函数在main之后,但是正如你所预料,简单的实现往往有问题。POSIX的atexit不是线程安全的,你需要用std::atexit才能保证线程安全。
但即使是std::atexit也不能正确处理单件之间的依赖,所以我们需要点更精细的办法,比如引用计数。
引用计数最大的好处是对象的生存期真正的对应于对象的“有效期”,使用引用计数的版本变成了这样:

std::shared_ptr<some_class> the_instance;
std::once_flag instance_created;
std::shared_ptr<some_class> get_instance() {
    std::call_once(
        instance_created,
        [](){ the_instance.reset(std::make_shared<some_class>(...)); }
    );
    return the_instance;
}

全局变量std::shared_ptr<some_class> the_instance保证一旦对象创建之后,直到程序结束,它至少有一个活引用,所以不会提前销毁;另外任何依赖于它的对象都会导致引用计数增加,所以可以正确的处理依赖性。
当然,这个实现的代价是API接口变成了std::shared_ptr<some_class>,因为无论是裸指针还是引用,都不能正确的实现引用计数的语义,如果你对此还是不满意,可以用Boehm GC实现一个兼容裸指针和引用的版本。

PS. LoadLibrary/dlopen最大的问题就是它们可能会跳过全局初始化的部分,也就是说,所有“逻辑上”应该在main之前执行的部分有可能根本就不会执行,由此会带来各种各样古怪的问题很难在这儿一一详述,总之,避免全局对象+LoadLibrary/dlopen这种组合会极大的改善你的生活品质。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值