最近在阅读muduo库的单例源码时发现其中Singleton模板的实现,简直堪称C++的编程艺术品。这里就记录一下从这其中所学到的一些思想。
(其余详细的单例模式介绍可以参考这篇文章[设计模式]C++单例模式的几种写法以及通用模板-CSDN博客)
首先贴出其源码:
网址在这muduo/muduo/base/Singleton.h at master · chenshuo/muduo
cpp
// 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> // atexit
namespace 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 detail
template<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++ 高级技巧解决了一系列传统单例模式中常见的问题。其核心思想可以概括为以下三点
-
利用 pthread_once 实现线程安全的懒汉式初始化
-
问题:传统的懒汉式单例在多线程环境下存在竞态条件,需要加锁保护,但加锁会带来性能开销。而饿汉式(程序启动时即创建)虽然线程安全,但无法解决跨编译单元的静态对象初始化顺序问题。
-
Muduo 的方案 :Muduo 采用了 POSIX 提供的 pthread_once 机制。该函数能保证在整个进程的生命周期内,其指定的初始化函数 (init) 绝对只会被执行一次,即使有多个线程在同一时刻尝试调用它 。它自带了高效的、无竞争的线程安全保障,完美地解决了多线程环境下的初始化问题,且避免了初始化顺序依赖的麻烦。
-
-
利用 SFINAE 技巧实现灵活的生命周期管理
-
问题:单例对象的销毁时机是一个难题。通常它会在程序结束时自动销毁,但有时我们希望在程序运行中途手动控制其销毁。
-
Muduo 的方案 :它使用了一种名为 SFINAE (Substitution Failure Is Not An Error, 替换失败并非错误) 的高级 C++ 模板元编程技巧 。
-
它在模板内部通过 sizeof 和模板重载,在编译期检查用户提供的类(如 IMServer)是否定义了一个名为 no_destroy_ 的成员。
-
如果用户没有定义 no_destroy_,单例模板就会自动注册一个 destroy 函数,在程序退出时通过 atexit 调用 delete 来销毁实例。
-
如果用户定义了 no_destroy_,SFINAE 规则会使自动销毁的代码路径在编译时被"忽略",从而将销毁的责任交还给用户。
-
这种方式让使用者可以自由选择由单例模板自动管理生命周期,还是由自己手动管理,极大地提高了灵活性 。
-
-
-
利用 sizeof 在编译期进行静态断言
-
问题:如果用户尝试将一个"不完整类型"(例如只有一个前向声明的类)实例化为单例,并在程序退出时尝试 delete 它,会导致未定义行为,这通常是运行时才能发现的严重错误。
-
Muduo 的方案:在 destroy 函数中,它使用 typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; 这样的代码进行检查。
-
如果T 是一个不完整的类型,sizeof(T) 的结果在某些编译器上是0。此时,代码试图定义一个大小为-1的数组,这在语法上是错误的,直接导致编译失败 。
-
这巧妙地将一个潜在的、危险的运行时错误,提前暴露为了一个编译期错误,大大增强了代码的健壮性和安全性 。
-
cpp
#ifndef MUDUO_BASE_SINGLETON_H
#define MUDUO_BASE_SINGLETON_H
#include <muduo/base/noncopyable.h>
#include <assert.h>
#include <pthread.h>
#include <stdlib.h> // atexit
namespace 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