想必每一位程序员都对设计模式中的单例模式非常的熟悉吧,以往我们用C++实现一个单例模式需要写以下代码:

 class CSingleton
{
private:
CSingleton() //构造函数是私有的
{
}
static CSingleton *m_pInstance;
public:
static CSingleton * GetInstance()
{
if (m_pInstance == NULL) //判断是否第一次调用
m_pInstance = new CSingleton();
return m_pInstance;
}
};

当然,这份代码在单线程环境下是正确无误的,但是当拿到多线程环境下时这份代码就会出现race condition,因此为了能在多线程环境下实现单例模式,我们首先想到的是利用同步机制来正确的保护我们的shared data,于是在多线程环境下单例模式代码就变成了下面这样:

 class CSingleton
{
private:
CSingleton() //构造函数是私有的
{
}
static CSingleton *m_pInstance;
mutex mtx;
public:
static CSingleton * GetInstance()
{
mtx.lock();
if (m_pInstance == NULL) //判断是否第一次调用
m_pInstance = new CSingleton();
mtx.unlock();
return m_pInstance;
}
};

正确是正确了,问题是每次调用GetInstance函数都要进入临界区,尤其是在heavy contention情况下函数将会成为系统的性能瓶颈,我们伟大的程序员发现我们不必每次调用GetInstance函数时都去获取锁,只是在第一次new这个实例的时候才需要同步,所以伟大的程序员们发明了著名的DCL技法,即Double Check Lock,代码如下:

 Widget* Widget::pInstance{ nullptr };
Widget* Widget::Instance() {
if (pInstance == nullptr) { // 1: first check
lock_guard<mutex> lock{ mutW };
if (pInstance == nullptr) { // 2: second check
pInstance = new Widget();
}
}
return pInstance;
}

曾今有一段时间,这段代码是被认为正确无误的,但是一群伟大的程序员们发现了其中的bug!并且联名上书表示这份代码是错误的。要解释其中为什么出现了错误,需要读者十分的熟悉memory model,这里我就不详细的说明了,一句话就是在这份代码中第三行代码:if (pInstance == nullptr)和第六行代码pInstance = new Widget();没有正确的同步,在某种情况下会出现new返回了地址赋值给pInstance变量而Widget此时还没有构造完全,当另一个线程随后运行到第三行时将不会进入if从而返回了不完全的实例对象给用户使用,造成了严重的错误。在C++11没有出来的时候,只能靠插入两个memory barrier来解决这个错误,但是C++11已经出现了好几年了,其中我认为最重要的是引进了memory model,从此C++11也能识别线程这个概念了!

  因此,在有了C++11后我们就可以正确的跨平台的实现DCL模式了,代码如下:

 atomic<Widget*> Widget::pInstance{ nullptr };
Widget* Widget::Instance() {
if (pInstance == nullptr) {
lock_guard<mutex> lock{ mutW };
if (pInstance == nullptr) {
pInstance = new Widget();
}
}
return pInstance;
}

C++11中的atomic类的默认memory_order_seq_cst保证了3、6行代码的正确同步,由于上面的atomic需要一些性能上的损失,因此我们可以写一个优化的版本:

 atomic<Widget*> Widget::pInstance{ nullptr };
Widget* Widget::Instance() {
Widget* p = pInstance;
if (p == nullptr) {
lock_guard<mutex> lock{ mutW };
if ((p = pInstance) == nullptr) {
pInstance = p = new Widget();
}
}
return p;
}

但是,C++委员会考虑到单例模式的广泛应用,所以提供了一个更加方便的组件来完成相同的功能:

 static unique_ptr<widget> widget::instance;
static std::once_flag widget::create;
widget& widget::get_instance() {
std::call_once(create, [=]{ instance = make_unique<widget>(); });
return instance;
}

可以看出上面的代码相比较之前的示例代码来说已经相当的简洁了,但是!!!有是但是!!!!在C++memory model中对static local variable,说道:The initialization of such a variable is defined to occur the first time control passes through its declaration; for multiple threads calling the function, this means there’s the potential for a race condition to define first.因此,我们将会得到一份最简洁也是效率最高的单例模式的C++11实现:

 widget& widget::get_instance() {
static widget instance;
return instance;
}

用Herb Sutter的话来说这份代码实现是“Best of All”的。

C++程序员们,快来写最简洁的单例模式吧的更多相关文章

  1. 程序员的修养 -- 如何写日志(logging)

      在程序中写日志是一件非常重要,但是很容易被开发人员忽视的地方.写好程序的日志可以帮助我们大大减轻后期维护压力. 在实际的工作中,开发人员往往迫于的巨大时间压力,而写日志又是一个非常繁琐的事情,往往 ...

  2. 程序员奇谈之我写的程序不可能有bug篇

    程序员在普通人的印象里是一份严(ku)谨(bi)的职业,也是一个被搞怪吐槽乐此不疲的职业,程序员们面对复杂的代码敲打电脑时连眉头都不会皱一下,但是有一个词却是他们痛苦的根源,它就是Bug. 有不少的新 ...

  3. .NET程序员如何快入门Spring Boot

    本篇文章将教你作为一个.NET程序员如何快入门Spring Boot.你不需要用Eclipse,也不需要用IDEA.已经习惯了VS,其他的IDE-- 但不得不说VS Code很厉害,一用就喜欢.微软给 ...

  4. 一个老程序员是如何手写Spring MVC的

    人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...

  5. 黑马程序员——File笔记读,写,复制

    #region ReadAllBytes byte[] buffer = File.ReadAllBytes(@"C:\Users\dell\Desktop\新建文件夹.txt") ...

  6. 不要困在自己建造的盒子里——写给.NET程序员(附精彩评论)

    此文章的主旨是希望过于专注.NET程序员在做好工作.写好.NET程序的同时,能分拨出一点时间接触一下.NET之外的东西(例如10%-20%的时间),而不是鼓动大家什么都去学最后什么都学不精,更不是说. ...

  7. IntelliJ下使用Code/Live Template加快编码速度:程序员的工作不是写程序,而是写程序解决问题

    程序员的工作不是写程序,而是写程序解决问题. --- 某不知名程序员 我们每天都在写代码,有些代码有结构性的相似,但不是所有的代码都可以被抽成方法.在这种情况下,我们应该考虑使用template的方式 ...

  8. 因写太多 BUG!程序员遭公司颁奖羞辱,做的一个比一个绝​

    刚入职的程序员新人,办公桌上,基本上也就一电脑.一键盘.一鼠标,再配个被杯子.然而混迹职场多年的猿老们,办公桌上都有一些彰显身份地位的“好东西”. 这张图两点颇多,最显眼的,是办公桌上那个黄黄的东西, ...

  9. 五月的仓颉大神写的 三年java程序员面试感悟 值得分享给大家

    感谢 五月的仓颉  的这篇文章 , 让我重新认识到自己身上的不足之处 .  原文地址http://www.cnblogs.com/xrq730/p/5260294.html,转载请注明出处,谢谢! 前 ...

随机推荐

  1. Ajax的同步与异步

    原文地址:http://www.cnblogs.com/Joetao/articles/3525007.html <%@ Page Language="C#" AutoEve ...

  2. chrome插件开发-消息机制中的bug与解决方案

    序言 最近开发chrome插件,涉及到消息传递机时按照教程去敲代码,结果总是不对.研究了大半天终于找到原因,现在记录下. 程序 插件程序参考官网 chrome官网之消息传递机制, 不能FQ的同事也可以 ...

  3. word 多级列表设置

    今天写论文碰到了这个问题, 希望能出现这样的效果: 第一章 1.1 1.2 第二章 2.1 2.2 ...... 为了达到这个效果,晕死了.因为我的标题不是普通的默认标题一标题二   比如同济一标题 ...

  4. ASP.NET连接远程Oracle数据库,提示试图加载格式不正确的程序

    VS调试远程连接Oracle数据库,一直报错 由于本地计算机是64位的操作系统,而且也确定安装的Oracle客户端是64位的 ,但是一直提示这个错误. 试了很多方法,终于发现可能是 不能在VS中调试的 ...

  5. 蛙蛙推荐:AngularJS学习笔记

    为了降低前端代码的数量,提高可维护性,可测试性,学习了下AngularJS,正在准备投入项目开发中. AngularJS的概念比较多,如果面向对象方面的书理解的不透的话学习起来有些费劲,它的官方有个快 ...

  6. Android 自定义View 总结

    Android系统本身给我们提供十分丰硕的组件让我们实现包罗万象的UI效果,与此同时,我们也能够非常方便实现各种方法来实现各种强大的功能.通过继承现有的UI控件,我们也能够拓展现有的功能.我们也能够完 ...

  7. 高性能网站架构设计之缓存篇(1)- Redis的安装与使用

    一.什么 Redis REmote DIctionary Server,简称 Redis,是一个类似于Memcached的Key-Value存储系统.相比Memcached,它支持更丰富的数据结构,包 ...

  8. EQueue - 一个C#写的开源分布式消息队列的总体介绍

    前言 本文想介绍一下前段时间在写enode时,顺便实现的一个分布式消息队列equeue.这个消息队列的思想不是我想出来的,而是通过学习阿里的rocketmq后,自己用c#实现了一个轻量级的简单版本.一 ...

  9. mongodb(副本集)

    副本集是mongo下的一种集群配置方式: 1.通过oplog的方式将主节点数据同步到副本节点,oplog不记录查询语句(因为不改变数据): 2.mongo的副本集可以有一个主节点,多个副本节点,主节点 ...

  10. 【C语言学习】《C Primer Plus》第6章 C控制语句:循环

    学习总结 1.循环的语法跟其他语言的没差多少,可能大多数语言都在C的基础上发展出来的,所以大同小异不奇怪. 2.在判断表达式里,C语言只有0被认为是假,所有非零值正整数都被认为真. #include ...