方式

  1. 友元
  2. 内部类 + 静态数据成员
  3. atexit
  4. pthread_once
  5. 饿汉模式 懒汉模式
  6. Meyers’ Singleton

内部类 + 静态数据成员

#include <iostream>
using namespace std;

class Singleton {
private:
  Singleton() = default;
  ~Singleton() = default;
  Singleton(const Singleton &) = delete;
  Singleton& operator=(const Singleton &) = delete;
  //不管返回类型,只要函数签名(函数名,参数)不同就能删

  class Deleter {
  public:
    ~Deleter() {
      if (Singleton::_instance) {
        delete Singleton::_instance;
        Singleton::_instance = nullptr;
      }
    }
  };

public:
  static Singleton &getinstance() { // 返回了一个Singleton类型对象的引用
    static Deleter deleter;//创建一次静态对象,之后就不会再创建
                           //且在程序销毁时销毁deleter时,deleter会销毁_instance
    if (!_instance) {
      _instance = new Singleton();
    }
    return *_instance;
  }
  void print() { cout << _a << endl; }

private:
  static Singleton *_instance;
  int _a = 123;
};
Singleton *Singleton::_instance = nullptr;

int main() { Singleton::getinstance().print(); }

饿汉懒汉

饿汉一开始就会创建对象

懒汉第一次调用才创建对象,多线程情况下,不安全。

  • 线程A还没创建完对象(_instance = new Singleton()),此时_instance仍为nullptr
  • 这时候线程B也来创建对象了,if判断_instance仍是nullptr,所以会产生两个对象,程序就有问题了

无脑锁的话代价高,if语句前先锁上,执行完创建再解锁,每次都要加与释放。

双检查锁

在if语句判断之后加锁,即判断为nullptr再加锁,就避免之后每次还加锁解锁了。

看似很完美,但有漏洞,因为内存读写的乱序执行(编译器的问题)

分析:_instance = new Singleton()这句话可以分成三个步骤来执行:

  1. 分配了一个Singleton类型对象所需要的内存。

  2. 在分配的内存处构造Singleton类型的对象。

  3. 把分配的内存的地址赋给指针_instance。

实际上只能确定步骤1是最先执行的 假如某个线程A是按照1,3,2的顺序执行,那么刚刚执行完步骤3给Singleton类型分配了内存(此时_instance就不是nullptr了)就切换到了线程B 由于_instance已经不是nullptr了,所以线程B会直接执行return *_instance得到一个对象,而这个对象并没有真正的被构造!!严重bug就这么发生了。

编译器可能会对代码进行调整优化,让那些看起来顺序不影响结果的代码进行顺序的调整,但是实际上在多线程中可能会有问题。

简单来说就是,可能会先分配内存,使_instance不为nullptr,但此时如果另一个线程也来创建单例对象,会发现不为nullptr,就会直接返回单例对象,但实际上该对象还没真正被创建。

c++11提供了原子操作解决该问题,感觉有点深,再说吧

ptrhead_once

在linux中,pthread_once()函数可以保证某个函数只执行一次。

Meyers’ Singleton

class Singleton {
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

使用局部静态变量来实现懒加载

c++11后可确保线程安全,可谓无敌。

更新时间: