当前位置: 首页 > news >正文

Muduo库中单例模式详解

最近在阅读muduo库的单例源码时发现其中Singleton模板的实现,简直堪称C++的编程艺术品。这里就记录一下从这其中所学到的一些思想。

(其余详细的单例模式介绍可以参考这篇文章[设计模式]C++单例模式的几种写法以及通用模板-CSDN博客)

首先贴出其源码:

网址在这muduo/muduo/base/Singleton.h at master · chenshuo/muduo

// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)#ifndef MUDUO_BASE_SINGLETON_H
#define MUDUO_BASE_SINGLETON_H#include "muduo/base/noncopyable.h"#include <assert.h>
#include <pthread.h>
#include <stdlib.h> // atexitnamespace muduo
{namespace detail
{
// This doesn't detect inherited member functions!
// http://stackoverflow.com/questions/1966362/sfinae-to-check-for-inherited-member-functions
template<typename T>
struct has_no_destroy
{template <typename C> static char test(decltype(&C::no_destroy));template <typename C> static int32_t test(...);const static bool value = sizeof(test<T>(0)) == 1;
};
}  // namespace detailtemplate<typename T>
class Singleton : noncopyable
{public:Singleton() = delete;~Singleton() = delete;static T& instance(){pthread_once(&ponce_, &Singleton::init);assert(value_ != NULL);return *value_;}private:static void init(){value_ = new T();if (!detail::has_no_destroy<T>::value){::atexit(destroy);}}static void destroy(){typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];T_must_be_complete_type dummy; (void) dummy;delete value_;value_ = NULL;}private:static pthread_once_t ponce_;static T*             value_;
};template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;template<typename T>
T* Singleton<T>::value_ = NULL;}  // namespace muduo#endif  // MUDUO_BASE_SINGLETON_H

Muduo 库中的单例实现非常经典和巧妙,它不仅仅是提供一个全局唯一的实例,更通过多种 C++ 高级技巧解决了一系列传统单例模式中常见的问题。其核心思想可以概括为以下三点

  1. 利用 pthread_once 实现线程安全的懒汉式初始化

    • 问题:传统的懒汉式单例在多线程环境下存在竞态条件,需要加锁保护,但加锁会带来性能开销。而饿汉式(程序启动时即创建)虽然线程安全,但无法解决跨编译单元的静态对象初始化顺序问题。

    • Muduo 的方案:Muduo 采用了 POSIX 提供的 pthread_once 机制。该函数能保证在整个进程的生命周期内,其指定的初始化函数 (init) 绝对只会被执行一次,即使有多个线程在同一时刻尝试调用它 。它自带了高效的、无竞争的线程安全保障,完美地解决了多线程环境下的初始化问题,且避免了初始化顺序依赖的麻烦。

  2. 利用 SFINAE 技巧实现灵活的生命周期管理

    • 问题:单例对象的销毁时机是一个难题。通常它会在程序结束时自动销毁,但有时我们希望在程序运行中途手动控制其销毁。

    • Muduo 的方案:它使用了一种名为 SFINAE (Substitution Failure Is Not An Error, 替换失败并非错误) 的高级 C++ 模板元编程技巧 。

      • 它在模板内部通过 sizeof 和模板重载,在编译期检查用户提供的类(如 IMServer)是否定义了一个名为 no_destroy_ 的成员。

      • 如果用户没有定义 no_destroy_,单例模板就会自动注册一个 destroy 函数,在程序退出时通过 atexit 调用 delete 来销毁实例。

      • 如果用户定义了 no_destroy_,SFINAE 规则会使自动销毁的代码路径在编译时被“忽略”,从而将销毁的责任交还给用户。

      • 这种方式让使用者可以自由选择由单例模板自动管理生命周期,还是由自己手动管理,极大地提高了灵活性 。

  3. 利用 sizeof 在编译期进行静态断言

    • 问题:如果用户尝试将一个“不完整类型”(例如只有一个前向声明的类)实例化为单例,并在程序退出时尝试 delete 它,会导致未定义行为,这通常是运行时才能发现的严重错误。

    • Muduo 的方案:在 destroy 函数中,它使用 typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; 这样的代码进行检查。

    • 如果T 是一个不完整的类型,sizeof(T) 的结果在某些编译器上是0。此时,代码试图定义一个大小为-1的数组,这在语法上是错误的,直接导致编译失败

    • 这巧妙地将一个潜在的、危险的运行时错误,提前暴露为了一个编译期错误,大大增强了代码的健壮性和安全性 。

#ifndef MUDUO_BASE_SINGLETON_H
#define MUDUO_BASE_SINGLETON_H#include <muduo/base/noncopyable.h>
#include <assert.h>
#include <pthread.h>
#include <stdlib.h> // atexitnamespace muduo
{namespace detail
{
// 这是一个模板元编程技巧,用于在编译期检查类型 T 是否含有 no_destroy_ 成员。
template<typename T>
struct has_no_destroy
{// 模板重载决议:尝试匹配这个版本。// &C::no_destroy_ 会检查 C 类型中是否存在名为 no_destroy_ 的成员。// 如果存在,这个模板函数就能被成功实例化。template <typename C> static char test(decltype(&C::no_destroy_));// 模板重载决议:如果上面的版本匹配失败(即替换失败),编译器不会报错,而是会尝试这个版本。 // "..." 是可变参数模板,可以匹配任何类型的任意数量参数,所以它总能匹配成功。template <typename C> static int32_t test(...);// const bool value = sizeof(test<T>(0)) == 1;// 核心思想:调用test<T>(0)。// 如果 T 类型有 no_destroy_ 成员,第一个 test 函数匹配成功,返回类型是 char,sizeof(char) == 1。// 如果 T 类型没有 no_destroy_ 成员,第一个 test 函数替换失败,匹配第二个,返回类型是 int32_t,sizeof(int32_t) == 4。// 因此,通过判断返回值的大小,就可以在编译期确定 T 是否有 no_destroy_ 成员。const static bool value = sizeof(test<T>(0)) == 1;
};
}template<typename T>
class Singleton : noncopyable // 继承 noncopyable 以禁止拷贝构造和赋值操作
{public:Singleton() = delete;   // 禁止构造~Singleton() = delete;  // 禁止析构static T& instance(){// pthread_once 保证在多线程环境下,init() 函数只会被执行一次。// 它解决了线程安全的懒汉式初始化问题,且没有性能锁的开销。pthread_once(&ponce_, &Singleton::init);assert(value_ != NULL);return *value_;}private:static void init(){value_ = new T(); // 创建单例对象实例// 使用 SFINAE 技巧判断用户类 T 是否自定义了 no_destroy_ 成员。if (!detail::has_no_destroy<T>::value){// 如果没有定义 no_destroy_,则注册 atexit(destroy),让程序在退出时自动销毁单例。::atexit(destroy);}}static void destroy(){// 这是一个编译期断言技巧,用于确保 T 是一个完整类型。// 如果 T 是不完整类型(如只有前向声明),sizeof(T) 会是 0,导致试图定义一个大小为 -1 的数组,从而引发编译错误。 // 这将潜在的运行时风险(删除不完整类型)转移到了编译期。 typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];(void) sizeof(T_must_be_complete_type); // 防止编译器警告未使用该类型定义delete value_;value_ = NULL;}private:// pthread_once 要求一个静态的 pthread_once_t 控制变量static pthread_once_t ponce_;// 全局唯一的实例指针static T* value_;
};// 静态成员变量的类外初始化
template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT; // 初始化 pthread_once 控制变量template<typename T>
T* Singleton<T>::value_ = NULL;}
#endif  // MUDUO_BASE_SINGLETON_H

http://www.dtcms.com/a/289507.html

相关文章:

  • 【Anaconda】Conda 虚拟环境打包迁移教程
  • 基于ACPs协议的智能体互联网示例(多智能体旅游规划)
  • JMeter连接数据库
  • Linux操作系统从入门到实战(十一)回车换行问题与用户缓冲区问题
  • C++虚函数易错点整理
  • 20250720-4-Kubernetes 调度-指定节点调度:nodeSelectornodeAffinity笔记
  • LeetCode 3202.找出有效子序列的最大长度 II:取模性质(动态规划)
  • JDK8默认垃圾回收器
  • (Python)类和类的方法进阶(基础教程介绍)(Python基础教程)
  • 利用核壳生物支架调控纤维 - 成骨稳态【AbMole】
  • Linux:线程控制
  • 【网络编程】网络传输-JSON
  • 【C语言】字符串与字符函数详解(下)
  • Shell脚本-cut工具
  • 从零到一MCP快速入门实战【1】
  • 疯狂星期四第13天运营日报
  • Java拓扑排序:2115 从给定原材料中找到所有可以做出的菜
  • Linux 基本指令详解
  • Self-Consistency:跨学科一致性的理论与AI推理的可靠性基石
  • WebDriver 对象中的方法
  • C++STL系列之list
  • Vue DIY 内容文本超出组件
  • Numpy库,矩阵形状与维度操作
  • 矩阵算法题
  • ZYNQ创新实践:免IIC驱动直控MCP4661T数字电位器
  • ollama基本配置
  • 仙盟数据库应用-外贸标签打印系统 前端数据库-V8--毕业论文-—-—仙盟创梦IDE
  • 数据库操作丨C++ 操作 数据库——SQLServer 篇
  • 数据库技术总结
  • 激光雷达和相机在线标定