单例模式(Singleton)是用于保证在整个应用程序的生命周期中的任何一个时刻,单例类的实例都只存在一个或者不存在。许多设备管理器常常设计为单例模式,比如很经典的电脑打印程序的设计,一台计算可以链接多台打印机,但是在整个打印过程中只能有一个打印程序的实例,不能让多台打印机打印同一份文件。

单例模式的实现

单例模式属于创建型模式,通过类本身来管理其唯一的实例,唯一的实例是类的一个普通对象。在设计这个类时,必须让他只能创建一个实例并提供对此实例的全局访问。也就是用户不能自己调用类的构造函数进行实例化,因此需要将类的构造函数、析构函数设计为类的私有成员函数。

饿汉模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class CSingleton {
  public:
    static CSingleton& getInstance()
    {
      return m_data;
    }
    
  private:
    static CSingleton m_data;
    Singleton(){}
    ~Singleton(){}
};

CSingleton CSingleton::m_data;  //static data member 在类中声明,在类外定义

饿汉模式下类在程序开始运行时就直接实例化,而不用等到需要该对象实例时在进行实例化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// .h

class SA {
public:
  static SA& getInstance()
  {
      return m_data;
  }

private:
  static SA m_data;
  SA();
  ~SA() {}
};

class SB {
public:
  static SB& getInstance()
  {
      return m_data;
  }
  void func()
private:
  static SB m_data; 
  SB();
  ~SB() {}
};

SA SA::m_data;
SB SB::m_data;

// .cpp

SA::SA() {
  cout << "this is SA constructor!" << endl;
  SB::getInstance().func();
}

SB::SB() {
  cout << "this is SB constructor!" << endl;
}

void SB::func()
{
  cout << "this is SB!" << endl;
}

// main.cpp
int main() {
  return 0;
}

程序输出以下结果:

this is SA constructor!
this is SB!
this is SB constructor!

可以看到SB类的成员函数func先于其构造函数的调用,也就是意味着在类实例化对象之前调用了类的成员函数,这是不安全的。通过分析代码可以知道,在执行到37~38行时候:

1
2
SA SA::m_data;
SB SB::m_data;

会依次调用SA和SB的构造函数,首先调用SA的构造函数:

1
2
3
4
SA::SA() {
    cout << "this is SA constructor!" << endl;
    SB::getInstance().func();
}

所以首先输出了this is SA constructor!,而后调用SB的成员函数func,虽然当前SB尚未定义(还未执行构造函数,即SB SB::m_data还未执行到),但是SB的类中保存着函数声明,故还是可以调用到SB的成员函数,输出this is SB!。当完成SA的构造函数后37行代码执行完毕,即SA的单例对象m_data初始化完成。此后将执行第38行SB SB::m_data;,即执行BS的构造函数,故输出this is SB constructor!

通过以上分析可以知道由于SA的构造函数会调用SB的成员函数,因此SB应该先于SA构造,所以可以通过交换第37和38行来实现:

1
2
SB SB::m_data;
SA SA::m_data;

懒汉模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// .h
class Singleton
{
public:
    static Singleton* getInstance()
    {
        if(! m_data) m_data = new Singleton();
        return m_data;
    }
    
private:
    static Singleton* m_data; //static data member 在类中声明,在类外定义
    Singleton(){}
    ~Singleton(){}
};

//.cpp
Singleton* Singleton::m_data = nullptr; 

懒汉模式下只有在getInstance()第一次被调用时才会创建实例,该模式能够保证静态成员的初始化顺序,而且也能够实现多态。但是该模式存在以下问题:

  • 线程不安全
    static Singleton* getInstance() 方法中,是通过 if 语句判断 静态实例变量 是否被初始化来觉得是否进行初始化,那么在多线程中就有可能出现多次初始化的问题。比方说,有两个多线程同时进入到这个方法中,一个线程运行到if语句进入后还没创建m_data,此时切换到另一线程,m_data的值还是false,同样进入到if语句里初始化变量,那么就会出现两次两次初始化静态实例变量的情况。

  • 内存泄漏
    在程序退出时析构函数并未执行,堆中申请的不会得到内存释放,将会导致资源泄露。

改进的懒汉模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Singleton {
public:
  static Singleton& getInstance() {
    static Singleton theSingleton;
    return theSingleton;
  }
 
private:
  Singleton();                            
  Singleton(Singleton const&);           
  Singleton& operator=(Singleton const&); 
  ~Singleton();     
}                     

通过使用静态局部变量,可以解决之前的版本的内存泄露的问题,但是这种版本的单例模式仍然存在线程不安全的问题,在局部作用域下的静态变量在编译时,编译器会创建一个附加变量标识静态变量是否被初始化,伪代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static Singleton &Instance()
{
  static bool constructed = false;
  static uninitialized Singleton instance_;
  if (!constructed) {
    constructed = true;
    new(&s) Singleton; 
  }
  return instance_;
}

当两个线程同时调用instance()时,一个线程运行到if语句进入后还没设constructed值,此时切换到另一线程,constructed值还是false,同样进入到if语句里初始化变量,两个线程都执行了这个单例类的初始化,从而违反了单例模式中实例唯一的原则。可以通过加锁的方式来改进这一问题,但是这样每次调用instance()都要加锁解锁,代价略大。

1
2
3
4
5
6
7
static Singleton &getInstance()
{
  Lock();
  static Singleton instance_;
  UnLock();
  return instance_;
}

为了避免频繁加锁解锁可以先对instance_加以判断,为空时再加锁。

boost的解决方案

在前面的方案中:饿汉模式中,使用到了类静态成员变量,但是遇到了初始化顺序的问题; 懒汉模式中,使用到了静态局部变量,但是存在着线程安全等问题。在boost 的实现方式是:单例对象作为静态局部变量,然后增加一个辅助类,并声明一个该辅助类的类静态成员变量,在该辅助类的构造函数中,初始化单例对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//.h

class SA
{
protected:
  struct object_creator
  {
      object_creator()
      {
          SA::instance();
      }
      inline void do_nothing() const {}
  };
  static object_creator create_object_;

  SA();
  ~SA(){};
public:
  static SA *instance()
  {
      static SA instance;
      return &instance;
  }
};
SA::object_creator SA::create_object_;
 
class SB
{
protected:
    SB();
    ~SB(){};
    struct object_creator
    {
        object_creator()
        {
            SB::instance();
        }
        inline void do_nothing() const {}
    };
    static object_creator create_object_;
public:
    static SB *instance()
    {
        static SB instance;
        return &instance;
    }
    void func();
};
 
SA::object_creator SA::create_object_;
SB::object_creator SB::create_object_;

结合饿汉模式中的.cpp文件,可以得到以下正确的输出:

this is SA constructor!
this is SB constructor!
this is SB!

分析程序执行流程可知,首先初始化SA类全局静态变量createobject ->调用object_creator的构造函数 ->调用SA::instance()方法初始化单例 ->执行SA的构造函数 ->调用SB::instance() ->初始化局部静态变量SB instance ->执行SB的构造函数,然后返回这个单例。

boost中的模板实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
template <typename T>
class 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 T& instance()
    {
        static T 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库通过添加一个类似 proxy-class 的方式,实现了单例模式,但是显然增加了复杂性,在实际应用中应该根据实际情况采用适当的实现方案。

C++11之后的实现方式

  • 使用c++11中的call_once实现。
1
2
3
4
5
6
Singleton* Singleton::m_instance;
Singleton* Singleton::getInstance() {
    static std::once_flag oc;//用于call_once的局部静态变量
    std::call_once(oc, [&] {  m_instance = new Singleton();});
    return m_instance;
}

或者,c++11标准里static局部变量初始化是线程安全的因此下面这种写法就是正确的,并且非常简洁。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Singleton
{
public:
    static Singleton* getInstance()
    {
        static Singleton instance;//c++11以后都是初始换线程安全的
        return &instance;
    }

protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            cout<<"Object_Creator constructor"<<endl;
            Singleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    Singleton() {cout<<"Singleton constructor"<<endl;}
    ~Singleton() {}
};

Singleton::Object_Creator Singleton::_object_creator;