单例模式 CPP - Xinrea/Learn GitHub Wiki

静态成员方式

类中所有成员均设为静态(static),例如:

class Singleton {
 private:
  static int data1; // 类内声明
  static int data2;
 public:
  static int getData1(){
    return data1;
  }
  static int getData2(){
    return data2;
  }
};
int Singleton::data1; // 类外定义
int Singleton::data2;

当然,这样声明的话,所有的成员均只会存在一份,实现了单例模式的基本要求,这种方法只是给全局变量及方法加上了一个类域而已。

使用静态成员函数的缺点如下所示:

  • 静态成员函数不能为const。静态成员函数只能够访问其参数、类的静态成员和全局变量,参数中并没有隐藏的this指针,而C++中const是用于修饰this指针的,因此静态成员函数不可能为const;同理,volatile也是如此,被volatile修饰的成员函数可以被volatile对象访问,volatile对象无法访问非volatile成员函数。
  • 静态成员函数不能为virtual。虚函数机制由虚函数指针和虚函数表实现,其中虚函数指针为类的普通成员变量,这意味着一个虚函数必定与一个实例对象紧密相关,没有对象的话,虚函数指针和虚函数表都不存在,就无从谈起“虚静态成员函数”了,因此staticvirtual相冲突,成员函数可不能可能既是静态的又是虚的。
  • 静态成员变量的初始化顺序难以控制。变量的初始化过程:基类的静态变量/全局变量->子类的静态变量/全局变量->基类的成员变量->子类的成员变量,其中静态成员变量的定义在类外,当静态成员变量间有初始化依赖顺序时,在类外的定义顺序就变得额外重要,如果顺序出错就会导致初始化出错,难以控制和修改。注意:const成员变量在进行列表初始化时,初始化顺序与列表中的顺序无关,而只与在内存中的位置-即在类内定义的顺序有关

饿汉模式

单例实例作为静态成员变量,类内声明类外定义,在静态变量初始化过程中生成单例,例如:

class Singleton {
 private:
  static Singleton instance;
  Singleton(){
  }
 public:
  static Singleton& getInstance(){
    return instance;
  }
};
Singleton Singleton::instance;

这种方式仍然存在初始化顺序的问题,由于单例在静态变量初始化过程中完成创建,多个单例类在此期间的初始化顺序时难以控制的,若它们之间有着依赖关系,在构造函数中用到了其他单例对象,则可能导致错误。

在使用时,应注意用引用来获得实例:

Singleton& ins = Singleton::getInstance();

否则会导致一次构造,多次解析。

懒汉模式

单例实例只在第一次使用时进行初始化。例如:

class Singleton {
 private:
  static Singleton* instance;
  Singleton(){
  }
  ~Singleton(){
  }
 public:
  static Singleton* getInstance(){
    if (instance == NULL) {
      instance = new Singleton();
    }
    return instance;
  }
};
Singleton* Singleton::instance = NULL;

缺点如下:

  • getInstance()中使用的if语句,在多线程情况下并不安全。
  • getInstance()获得的是单例的指针,无法自动析构。 可以添加一个Destructor()方法,来手动析构。

懒汉模式改进

使用局部静态变量,当进入getInstance()方法中时,局部变量初始化。例如:

class Singleton {
 private:
  Singleton(){
  }
 public:
  static Singleton& getInstance(){
    static Singleton instance;
    return instance;
  }
};

缺点:

  • 任意两个单例类的构造函数(getInstance())不能够互相引用对方的实例。
  • 单例的实例互相引用时,注意析构带来的问题。
  • 多线程下,编译器仍然是相当于使用if语句判断instance是否初始化,因此也是多线程不安全的。为了解决多线程的问题,可以向instance加锁,但是初始化后,每次getInstance()仍会经历锁的过程,导致效率下降。

最终实现

boost的实现方式:综合局部静态变量和类静态全局变量,使用代理类的静态全局变量的构造函数,来调用getInstance()生成局部静态变量,即在静态变量/全局变量初始化的过程中,完成了单例类的实例化。例如:

class Singleton {
 private:
  Singleton(){
  }
 protected:
  class Proxy {
    Proxy(){
      Singleton::getInstance();
    }
  };
  static Proxy proxy;
 public:
  static Singleton& getInstance(){
    static Singleton instance;
    return instance;
  }
};
Singleton::Proxy Singleton::proxy;