C++实现单例模式-多种方式比较

简介: 单例模式,面试中经常被问到,但是很多人只会最简单的单例模型,可能连多线程都没考虑到,本文章从最简单的单例,到认为是最佳的单例模式实现方式,单例模式没有什么知识点,直接上源码

说明

单例模式,面试中经常被问到,但是很多人只会最简单的单例模型,可能连多线程都没考虑到,本文章从最简单的单例,到认为是最佳的单例模式实现方式,单例模式没有什么知识点,直接上源码


源代码

版本一

这个版本是最简单的版本,但是存在2个问题:(1)不支持多线程(2)需要主动调用函数来释放对象,否则程序结束后,不会调用析构函数,这可能会造成严重的后果,比如单例类对象需要在释放时做一些网络数据传输的工作,最后释放连接,在关闭连接前,会做一些判断,是否可以释放连接,可以释放连接时,才去释放连接,这些都可以在析构函数里完成

//缺点:
//1.需要主动去释放单例对象,否则会造成内存泄漏
//2.不支持多线程
class Singleton
{
public:
  static Singleton* GetInstance() {
  if (_instance == nullptr)
  {
    _instance = new Singleton();
  }
  return _instance;
  }
  //
  static void freeInstance()
  {
  if (_instance != nullptr) {
    delete _instance;
  }
  _instance = nullptr;
  }
private:
  Singleton() { std::cout << "new Singleton" << std::endl; }
  ~Singleton() { std::cout << "~Singleton" << std::endl; }//
  Singleton(const Singleton& clone) = delete;
  Singleton& operator=(const Singleton&) = delete;
  //存放在静态区,程序结束,生命周期结束,只是将指针设置为nullptr,并没有去调用析构函数,如果想被释放,
  //必须加一个函数去主动释放,那么通常就需要将释放函数放到程序结束的位置(否则单例就没有意义),如果一个系统中需要使用多个单例
  //会调用释放函数,不方便
  static Singleton* _instance;
};
Singleton* Singleton::_instance = nullptr;//静态变量必须初始化

版本2

版本1需要主动去释放对象不能主动释放对象的空间,有了版本2

//版本1需要主动释放单例,不太方便,因此这里解决主动释放的问题,使用atexit:在程序结束后自动去调用析构函数,释放资源
class Singleton2 {
public :
  static Singleton2* GetInstance()
  {
  if (_singleton == nullptr)
  {
    _singleton = new Singleton2();
    atexit(destructor);//释放操作,不会造成内存泄漏或者资源没有被释放的问题
  }
  return _singleton;
  }
private:
  static void destructor() {
  if (nullptr != _singleton)
  {
    delete _singleton;
  }
  _singleton = nullptr;
  }
  Singleton2() { std::cout << "new Singleton2" << std::endl; }
  ~Singleton2() { std::cout << "~Singleton2" << std::endl; }
  Singleton2(const Singleton2& cpy) = delete;
  Singleton2& operator=(const Singleton2& other) = delete;
  static Singleton2* _singleton;
};
Singleton2* Singleton2::_singleton = nullptr;//静态变量必须初始化

版本3

解决版本1的2个问题,但是这个版本有一个隐含的知识点,很多人不知道这个知识点,就很难发现这个版本的问题所在

//这个例子好像没什么问题,一般人也很难发现,但是这个如果上线,会出现莫名其妙的错误,主要原因在new实例的时候说明
class Singleton3 {
public:
  static Singleton3* GetInstance()
  {
  //std::lock_guard<std::mutex> lock(mutex);//枷锁不要加在这里,这里锁的范围太大了,单例模式,主要是读取
  //如果在这里枷锁,基本每次调GetInstance都会枷锁,性能不高。
  if (instance == nullptr)
  {
    std::lock_guard<std::mutex> lock(mutex);
    //为什么需要双重判断,有可能个进程同时进入到外层if(instance == nullptr),这时候一个线程进入到这里
    //另一个线程会卡住,当第一个线程释放时,如果不进行null判断,会造成构造2次
    if (instance == nullptr)
    {
    //下一句主要分为3个步骤
    //1.分配空间malloc
    //2.构造函数初始化
    //3.赋值给instance
    //通常情况下,cpu为了效率,可能会出现1,2,3,也可能会出现1,3,2
    //当执行顺序为1,3,2时,当这个线程执行完1,3步骤时,假如另一个线程恰好执行到外层if,那么由于已经赋值
    //但是赋值却不是初始化的值,其他线程去执行的时候拿到的就是错误的数据,这就是多线程会导致的问题。
    //可以使用内存屏障解决,这里就不讲了,请自行参考其他文章
    instance = new Singleton3();// 
    atexit(deconstractor);
    }
  }
  return instance;
  }
private:
  static void deconstractor() {
  if (instance != nullptr)
  {
    delete instance;
  }
  instance = nullptr;
  }
  Singleton3() { std::cout << "new Singleton3" << std::endl; }
   ~Singleton3() { std::cout << "~Singleton3" << std::endl; }
   Singleton3(const Singleton3& cpy) = delete;
   Singleton3& operator=(const Singleton3& other) = delete;
   static Singleton3* instance;
   static std::mutex mutex;
};
Singleton3* Singleton3::instance = nullptr;
std::mutex Singleton3::mutex;

版本4

解决CPU reorder的问题,比较好的解决方案

//局部静态对象,生命周期结束后会自动释放空间,
//而且static也是线程安全的,假如2个线程同时调用GetInstance,不会创建2个instance
class Singleton5 {
public:
  static Singleton5& GetInstance() {
  static Singleton5 instance;
  return instance;
  }
private:
  Singleton5() { std::cout << "new Singleton5" << std::endl; }
  ~Singleton5() { std::cout << "~Singleton5" << std::endl; }
  Singleton5(const Singleton5&) = delete;
  Singleton5& operator=(const Singleton5& other) = delete;
};

版本6

版本5 不利于扩展,使用模板来解决所有类的单例模式

//上面一个版本已经很好了,但是扩展性差,只能用于一个类的单例,使用模板来实现任何类的单例
template <typename T>
class Singleton6{
public:
  static T& GetInstance() { //T是子类,返回子类对象的引用
  static T instance;
  return instance;
  }
protected://子类可访问父类的构造函数,初始化和释放对象时,都会调用父类的构造函数
  Singleton6() { std::cout << "new Model Singleton6" << std::endl; }
  ~Singleton6() { std::cout << "~Model Singleton6" << std::endl; }
  Singleton6(const Singleton6&) = delete;
  Singleton6& operator=(const Singleton6& other) = delete;
};
//注意,父类为模板类Singleton6<DesignPattern>,子类为DesignPattern,将子类传递到父类
class DesignPattern :public Singleton6<DesignPattern>
{
 //由于对象是在父类对象构造的,而我们将构造和析构设置为private,父类要想访问,就需要将其设置为友元类
 friend Singleton6<DesignPattern>;
private:
  DesignPattern() { std::cout << "new DesignedPattern" << std::endl; }
  ~DesignPattern() { std::cout << "~DesignPattern" << std::endl; }
  DesignPattern(const DesignPattern& cpy) = delete;
  DesignPattern& operator=(const DesignPattern& other) = delete;
};


相关文章
|
2月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
62 0
|
2月前
|
设计模式 安全 测试技术
【C++】—— 单例模式详解
【C++】—— 单例模式详解
|
7月前
|
设计模式 存储 安全
设计模式之单例模式(C++)
设计模式之单例模式(C++)
|
7月前
|
设计模式 安全 Java
特殊类设计及单例模式(C++)
特殊类设计及单例模式(C++)
65 1
|
7天前
|
设计模式 Java C++
【C++高阶(八)】单例模式&特殊类的设计
【C++高阶(八)】单例模式&特殊类的设计
|
7天前
|
设计模式 安全 编译器
【代码片段】【C++】C++11线程安全单例模式
【代码片段】【C++】C++11线程安全单例模式
13 1
|
2月前
|
设计模式 存储 缓存
【ffmpeg C++ 播放器优化实战】优化你的视频播放器:使用策略模式和单例模式进行视频优化
【ffmpeg C++ 播放器优化实战】优化你的视频播放器:使用策略模式和单例模式进行视频优化
58 0
|
2月前
|
设计模式 存储 缓存
设计模式之单例模式(C++)
设计模式之单例模式(C++)
25 2
|
4月前
|
设计模式 安全 编译器
c++单例模式-6种单例层层迭代优化
6种单例模式,层层迭代优化
17 1
|
4月前
|
C++
【C++ 单例模式】
【C++ 单例模式】