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

2020-01-06 14:38:35王冬梅

方案一


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 constructorn");
  QMSqlite::instance()->do_something();
}
 
QMSqlite::QMSqlite()
{
  printf("QMSqlite constructorn");
}
void QMSqlite::do_something()
{
  printf("QMSqlite do_somethingn");
}