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;
};


相关文章
|
7月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
145 0
|
7月前
|
设计模式 安全 测试技术
【C++】—— 单例模式详解
【C++】—— 单例模式详解
|
2月前
|
C++
C++单例模式
C++中使用模板实现单例模式的方法,并通过一个具体的类A示例展示了如何创建和使用单例。
32 2
|
7月前
|
设计模式 安全 算法
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
53 0
|
4月前
|
安全 C++
C++ QT 单例模式
C++ QT 单例模式
69 0
|
4月前
|
设计模式 安全 IDE
C++从静态类型到单例模式
C++从静态类型到单例模式
38 0
|
5月前
|
设计模式 安全 C++
C++一分钟之-C++中的设计模式:单例模式
【7月更文挑战第13天】单例模式确保类只有一个实例,提供全局访问。C++中的实现涉及线程安全和生命周期管理。基础实现使用静态成员,但在多线程环境下可能导致多个实例。为解决此问题,采用双重检查锁定和`std::mutex`保证安全。使用`std::unique_ptr`管理生命周期,防止析构异常和内存泄漏。理解和正确应用单例模式能提升软件的效率与可维护性。
62 2
|
6月前
|
设计模式 存储 缓存
C++ -- 单例模式
**摘要:** 单例模式确保一个类仅有一个实例,并提供全局访问点。为了实现单例,构造函数通常设为私有,通过静态成员函数来创建和返回实例。两种常见实现是饿汉模式(在类加载时创建实例,线程安全但可能导致不必要的内存占用)和懒汉模式(首次使用时创建,可能需线程同步)。拷贝构造函数和赋值运算符通常被禁用来防止额外实例的创建。单例模式适用于资源管理、缓存和线程池等场景。在C++中,静态成员变量和函数用于存储和访问单例实例,保证其生命周期与程序相同。
|
7月前
|
安全 程序员 C语言
从C语言到C++_37(特殊类设计和C++类型转换)单例模式(下)
从C语言到C++_37(特殊类设计和C++类型转换)单例模式
56 5
|
7月前
|
设计模式 编译器 Linux
从C语言到C++_37(特殊类设计和C++类型转换)单例模式(中)
从C语言到C++_37(特殊类设计和C++类型转换)单例模式
44 0