使用设计模式中的单例模式来实现C++的boost库_C 语言

线程安全的单例模式
一、懒汉模式
:即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例。

需要用锁,来保证其线程安全性:原因:多个线程可能进入判断是否已经存在实例的if语句,从而non thread safety。

使用double-check来保证thread safety。但是如果处理大量数据时,该锁才成为严重的性能瓶颈。

1、静态成员实例的懒汉模式:

class Singleton
{
private:
  static Singleton* m_instance;
  Singleton(){}
public:
  static Singleton* getInstance();
};

Singleton* Singleton::getInstance()
{
  if(NULL == m_instance)
  {
    Lock();
//借用其它类来实现,如boost
    if(NULL == m_instance)
    {
      m_instance = new Singleton;
    }
    UnLock();
  }
  return m_instance;
}

2、内部静态实例的懒汉模式

这里需要注意的是,C++0X以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。但C++ 0X以前,仍需要加锁。

class SingletonInside
{
private:
  SingletonInside(){}
public:
  static SingletonInside* getInstance()
  {
    Lock();
// not needed after C++0x
    static SingletonInside instance;
    UnLock();
// not needed after C++0x
    return instance;
  }
};

二、饿汉模式:即无论是否调用该类的实例,在程序开始时就会产生一个该类的实例,并在以后仅返回此实例。

由静态初始化实例保证其线程安全性,WHY?因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题。

故在性能需求较高时,应使用这种模式,避免频繁的锁争夺。

class SingletonStatic
{
private:
  static const SingletonStatic* m_instance;
  SingletonStatic(){}
public:
  static const SingletonStatic* getInstance()
  {
    return m_instance;
  }
};

//外部初始化 before invoke main
const SingletonStatic* SingletonStatic::m_instance = new SingletonStatic;

boost库的实现示例

单例本来是个很简单的模式,实现上应该也是很简单,但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库这样的实现像打了几个补丁,用了一些奇技淫巧,虽然确实绕过了坑实现了需求,但感觉挺不好的。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索c++
, 设计模式
, 单例模式
boost
boost 单例模式、boost 单例、boost 单例模板、枚举实现单例模式、java枚举实现单例模式,以便于您获取更多的相关知识。

时间: 2024-09-20 17:00:19

使用设计模式中的单例模式来实现C++的boost库_C 语言的相关文章

C++设计模式编程中使用Bridge桥接模式的完全攻略_C 语言

桥接模式将抽象(Abstraction)与实现(Implementation)分离,使得二者可以独立地变化. 桥接模式典型的结构图为: 在桥接模式的结构图中可以看到,系统被分为两个相对独立的部分,左边是抽象部分,右边是实现部分,这两个部分可以互相独立地进行修改:例如上面问题中的客户需求变化,当用户需求需要从 Abstraction 派生一个具体子类时候,并不需要像上面通过继承方式实现时候需要添加子类 A1 和 A2 了.另外当上面问题中由于算法添加也只用改变右边实现(添加一个具体化子类),而右边

C/C++中static,const,inline三种关键字详细总结_C 语言

一.关于staticstatic 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因.作用谈起,全面分析static 修饰符的实质. static 的两大作用: 一.控制存储方式 static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间. 引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保

C++中头文件的概念与基本编写方法_C 语言

1 标准库中的头文件C++标准库中的一切内容都被放在名字空间std中(名字空间中的内容对外是不可见的),但是带来了一个新问题,无数现有的C++代码都依赖于使用了多年的伪标准库中的功能,如声明在<iostream.h>等头文件中的功能,使用std包装标准库导致现有代码的不可用,为了兼容这种情况,标准委员会为包装了std的那部分标准库创建了新的头文件,新的头文件的文件名与旧的一样,只是没有.h这个后缀,如<iostream.h>就变成了<iostream>.对于C头文件,采

iOS App开发中使用设计模式中的单例模式的实例解析_IOS

一.单例的作用顾名思义,单例,即是在整个项目中,这个类的对象只能被初始化一次.它的这种特性,可以广泛应用于某些需要全局共享的资源中,比如管理类,引擎类,也可以通过单例来实现传值.UIApplication.NSUserDefaults等都是IOS中的系统单例. 二.单例模式的两种写法 1,常用写法 #import "LGManagerCenter.h" static LGManagerCenter *managerCenter; @implementation LGManagerCen

简单讲解在Java编程中实现设计模式中的单例模式结构_java

1. 模式介绍 模式的定义 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 模式的使用场景 确保某个类有且只有一个对象的场景,例如创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源. 2. UML类图 角色介绍: (1)Client : 高层客户端. (2)Singleton : 单例类. 3. 模式的简单实现 public class Singleton { private static Singleton intance; private Singleton(

JavaScript实现设计模式中的单例模式的一些技巧总结_javascript技巧

一.使用全局变量保存单例 这是最简单的实现方法 function Person(){ this.createTime=new Date(); } var instance=new Person(); function getInstance(){ return instance; } 加载该js时就创建一个Person对象,保存到instance全局变量中,每次使用都取这个对象.如果一次都没使用,那么创建的这个对象则浪费了,我们可以优化一下, var instance function getI

php实现设计模式中的单例模式详解_php技巧

[概要] 保证一个类仅有一个实例,并且提供一个访问它的全局访问点[GOF95] [特点] 1.一个类只有一个实例 2.它必须自行创建这个实例 3.必须自行向整个系统提供这个实例 [结构图] [主要角色] Singleton定义一个Instance操作,允许客户访问它的唯一实例.Instance是一个类方法.负责创建它的唯一的实例. [优缺点] 1.对唯一实例的受控访问 2.缩小命名空间 单例模式是对全局变量的一种改进.它避免了那些存储唯一实例的全局变量污染命名空间 3.允许对操作和表示的精华 单

C++设计模式编程之Flyweight享元模式结构详解_C 语言

由遇到的问题引出享元模式: 在面向对象系统的设计何实现中,创建对象是最为常见的操作.这里面就有一个问题:如果一个应用程序使用了太多的对象,就会造成很大的存储开销.特别是对于大量轻量级(细粒度)的对象,比如在文档编辑器的设计过程中,我们如果为没有字母创建一个对象的话,系统可能会因为大量的对象而造成存储开销的浪费.例如一个字母"a"在文档中出现了100000 次,而实际上我们可以让这一万个字母"a"共享一个对象,当然因为在不同的位置可能字母"a"有不

c++函数中的指针参数与地址参数区别介绍_C 语言

比如 一个函数 chat(link &a): chat(ling *a): 前者引入一个地址做形参 是不是可以把一个指针变量p.. 这么用chat(p): 那跟第二个函数 有什么区别呢 都是传地址啊.. 小弟弄不明白~~ chat(int&a); chat(int *a); 这两个函数是完全不同意义的东西,你的理解主要是在int&a和int* a这个类型上面.要注意int&和int*是两个完全不同的类型.int&是引用类型,而int*是指向int类型变量的指针类型.