C++ 单例模式的各种坑及最佳实践

这篇具有很好参考价值的文章主要介绍了C++ 单例模式的各种坑及最佳实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

单例模式是设计模式中最简单、常见的一种。其主要目的是确保整个进程中,只有一个类的实例,并且提供一个统一的访问接口。常用于 Logger 类、通信接口类、线程池等。

基本原理

限制用户直接访问类的构造函数,提供一个统一的 public 接口获取单例对象。

这里有一个“先有鸡还是先有蛋”的问题:

  • 因为用户无法访问构造函数,所以无法创建对象
  • 因为无法创建对象,所以不能调用普通的 getInstance() 方法来获取单例对象

解决这个问题的方法很简单,将 getInstance() 定义为 static 即可(这也会限制 getInstance() 内只能访问类的静态成员)

注意事项

  • 所有的构造函数是 private 的
  • 拷贝构造、拷贝赋值运算符需要显式删除 =delete,防止编译器自动合成(即使你显式定义了析构函数或拷贝构造/拷贝赋值运算符,编译器依然可能合成拷贝赋值运算符/拷贝构造!新的 C++ 标准已将该行为标记为 deprecated,但为了兼容旧代码,目前 C++23 仍然会合成!后面打算单独用一篇笔记总结一下编译器默认合成的函数)

C++ 单例模式的几种实现方式

版本 1 饿汉式

提前创建单例对象

class Singleton1 {
   public:
    static Singleton1* getInstance() { return &inst; }
    Singleton1(const Singleton1&) = delete;
    Singleton1& operator=(const Singleton1&) = delete;

   private:
    Singleton1() = default;
    static Singleton1 inst;
};

Singleton1 Singleton1::inst;

这个版本在程序启动时创建单例对象,即使没有使用也会创建,浪费资源。

版本 2 懒汉式

版本 2 通过将单例对象的实例化会推迟到首次调用 getInstance(),解决了上面的问题。

class Singleton2 {
   public:
    static Singleton2* getInstance() {
        if (!pSingleton) {
            pSingleton = new Singleton2();
        }
        return pSingleton;
    }
    Singleton2(const Singleton2&) = delete;
    Singleton2& operator=(const Singleton2&) = delete;

   private:
    Singleton2() = default;
    static Singleton2* pSingleton;
};

Singleton2* Singleton2::pSingleton = nullptr;

版本 3 线程安全

在版本 2 中,如果多个线程同时调用 getInstance() 则有可能创建多个实例。

class Singleton3 {
   public:
    static Singleton3* getInstance() {
        lock_guard<mutex> lck(mtx);
        if (!pSingleton) {
            pSingleton = new Singleton3();
        }
        return pSingleton;
    }
    Singleton3(const Singleton3&) = delete;
    Singleton3& operator=(const Singleton3&) = delete;

   private:
    Singleton3() = default;
    static Singleton3* pSingleton;
    static mutex mtx;
};

Singleton3* Singleton3::pSingleton = nullptr;
mutex Singleton3::mtx;

加锁可以解决线程安全的问题,但版本 3 的问题在于效率太低。每次调用 getInstance() 都需要加锁,而加锁的开销又是相当高昂的。

版本 4 DCL (Double-Checked Locking)

版本 4 是版本 3 的改进版本,只有在指针为空的时候才会进行加锁,然后再次判断指针是否为空。而一旦首次初始化完成之后,指针不为空,则不再进行加锁。既保证了线程安全,又不会导致后续每次调用都产生锁的开销。

class Singleton4 {
   public:
    static Singleton4* getInstance() {
        if (!pSingleton) {
            lock_guard<mutex> lck(mtx);
            if (!pSingleton) {
                pSingleton = new Singleton4();
            }
        }
        return pSingleton;
    }
    Singleton4(const Singleton4&) = delete;
    Singleton4& operator=(const Singleton4&) = delete;

   private:
    Singleton4() = default;
    static Singleton4* pSingleton;
    static mutex mtx;
};

Singleton4* Singleton4::pSingleton = nullptr;
mutex Singleton4::mtx;

DCL 在很长一段时间内被认为是 C++ 单例模式的最佳实践。但也有文章表示 DCL 的正确性取决于内存模型,关于这部分的深入讨论可以参考这两篇文章:

  • https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
  • https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/

版本 5 Meyers’ Singleton

这个版本利用局部静态变量来实现单例模式。最早由 C++ 大佬、Effective C++ 系列的作者 Scott Meyers 提出,因此也被称为 Meyers’ Singleton

"This approach is founded on C++'s guarantee that local static objects are initialized when the object's definition is first encountered during a call to that function." ... "As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object."
—— Scott Meyers

TLDR:这就是 C++11 之后的单例模式最佳实践,没有之一。

  • 最简洁:不需要额外定义类的静态成员
  • 线程安全,不需要额外加锁
  • 没有烦人的指针
class Singleton5 {
   public:
    static Singleton5& getInstance() {
        static Singleton5 inst;
        return inst;
    }

    Singleton5(const Singleton5&) = delete;
    Singleton5& operator=(const Singleton5&) = delete;

   private:
    Singleton5() = default;
};

我曾见到过有人画蛇添足地返回单例指针,就像下面这样

static Singleton* getInstance() {
    static Singleton inst;
    return &inst;
}

如果没什么正当的理由(我也实在想不到有什么理由),还是老老实实地返回引用吧。现代 C++ 应当避免使用裸指针,关于这一点,我也有一篇笔记:裸指针七宗罪文章来源地址https://www.toymoban.com/news/detail-479160.html

到了这里,关于C++ 单例模式的各种坑及最佳实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【C++】设计模式-单例模式

    【C++】设计模式-单例模式

    目录 一、单例模式 单例模式的三个要点 针对上述三要点的解决方案 常用的两类单例模式  二、懒汉模式实现 1.基本实现 2.锁+静态成员析构单例 3.双层检查锁定优化 4.双层检查锁定+智能指针 三、饿汉模式实现 1.基础实现 2.嵌套内部类解决内存泄漏 3.智能指针解决内存泄漏

    2024年02月16日
    浏览(8)
  • 【设计模式】C++单例模式详解

    ⼀个类仅有⼀个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。 那么,我们必须保证: 该类不能被复制;也不能被公开的创造。 对于 C++ 来说,它的构造函数,拷贝构造函数和赋值函数都不能被公开调用。 单例模式又分为 懒汉模式 和 饿汉模式 ,它们

    2024年02月05日
    浏览(9)
  • C++设计模式代码--单例模式

    参考:5. 单例模式(Singleton) (yuque.com) 1、什么是单例模式 保证一个类只有一个实例,并提供一个访问该实例的全局节点; 2、什么情况下需要单例模式 某个类的对象在软件运行之初就创建,并且在软件的很多地方都需要读写这个类的信息;使用单例模式的话,类对象就只要

    2024年02月03日
    浏览(20)
  • C++设计模式:单例模式(十)

    C++设计模式:单例模式(十)

    1、单例设计模式 单例设计模式,使用的频率比较高,整个项目中某个特殊的类对象只能创建一个 并且该类只对外暴露一个public方法用来获得这个对象。 单例设计模式又分懒汉式和饿汉式,同时对于懒汉式在多线程并发的情况下存在线程安全问题 饿汉式:类加载的准备阶段

    2024年04月14日
    浏览(19)
  • Java 设计模式最佳实践:6~9

    原文:Design Patterns and Best Practices in Java 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN Java 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 这一章将描述反应式编程范式,以及为什么它能很好地适用于带有函数元素的语言。读者将熟悉反应式编程背后的概念。我们

    2023年04月14日
    浏览(9)
  • C++ 单例模式

    C++ 单例模式跟Java中的单例模式没什么区别 什么是单例?  单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例 什么时候使用单例 1个类里面的方法要在很多地方都使用到的时候建议使用单例。  单例的3个步骤,也是判断是否是单例的依据 1 就是

    2024年02月05日
    浏览(8)
  • C++ 【单例模式】

    C++ 【单例模式】

    简单介绍 单例模式是一种 创建型设计模式 | 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 它也会破坏代码的模块化特性,因为单例模式往往会 承担了很多的职责 ,导致与 其他模块 产生过多的耦合 基础理解 单一职责原则: 一个类或模块应该只有

    2024年04月12日
    浏览(9)
  • c++——单例模式

    单例模式确保一个类只有一个实例,并提供一个全局访问点以获取该实例。这通常通过让类的构造函数为私有,以防止外部直接实例化,然后提供一个静态方法来获取实例。 实现单例模式的主要步骤包括: ①将类的构造函数和拷贝构造函数声明为私有,以防止外部直接实例

    2024年02月12日
    浏览(4)
  • 单例模式——C++版本

    在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。 类中多对象的操作函数有如下几个: 构造函数 : 能够创建出一个新对象; 拷贝构造函数 :能够根据一个已经存在的对象拷贝出一个新对

    2024年02月09日
    浏览(9)
  • 【C++基础】单例模式

    本文章参考:单例模式 - 巴基速递 | 爱编程的大丙 什么是单例模式 在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。 如果使用单例模式,首先要保证这

    2024年02月07日
    浏览(9)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包