前言
Apollo 中大量地应用了单例设计模式,使用双检锁 + 原子变量的懒汉式单例实现,其中还有一种实现,一个普通的SensorManager
类经宏定义 DECLARE_SINGLETON(SensorManager)
修饰成为单例类:
实现分析
class SensorManager {
// ...
//
// other code
//
// ...
DECLARE_SINGLETON(SensorManager)
};
DECLARE_SINGLETON(classname)
定义在apollo/cyber/common/macros.h
中:
#define CYBER_COMMON_MACROS_H_
#include <iostream>
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>
#include "cyber/base/macros.h"
DEFINE_TYPE_TRAIT(HasShutdown, Shutdown)
template <typename T>
typename std::enable_if<HasShutdown<T>::value>::type CallShutdown(T *instance) {
instance->Shutdown();
}
template <typename T>
typename std::enable_if<!HasShutdown<T>::value>::type CallShutdown(
T *instance) {
(void)instance;
}
// There must be many copy-paste versions of these macros which are same
// things, undefine them to avoid conflict.
#undef UNUSED
#undef DISALLOW_COPY_AND_ASSIGN
#define UNUSED(param) (void)param
#define DISALLOW_COPY_AND_ASSIGN(classname) \
classname(const classname &) = delete; \
classname &operator=(const classname &) = delete;
#define DECLARE_SINGLETON(classname) \
public: \
static classname *Instance(bool create_if_needed = true) { \
static classname *instance = nullptr; \
if (!instance && create_if_needed) { \
static std::once_flag flag; \
std::call_once(flag, \
[&] { instance = new (std::nothrow) classname(); }); \
} \
return instance; \
} \
\
static void CleanUp() { \
auto instance = Instance(false); \
if (instance != nullptr) { \
CallShutdown(instance); \
} \
} \
\
private: \
classname(); \
DISALLOW_COPY_AND_ASSIGN(classname)
#endif // CYBER_COMMON_MACROS_H_
可以看到,DECLARE_SINGLETON(classname) 在预处理阶段会被替换为:
- 静态方法
Instance
- 私有的泛化默认构造函数和嵌套的宏定义
DISALLOW_COPY_AND_ASSIGN
- 静态方法
CleanUp
Instance 方法
提供对唯一实例的全局访问点
实例的唯一性通过局部静态的实例指针实现:
static classname *instance = nullptr;
泛化的单例
我们逐点分析 DECLARE_SINGLETON(classname)
是如何将任意一个类修饰为单例类的:
(1) 提供对唯一实例的全局访问点
实例的唯一性通过局部静态( local static
)的实例指针实现:
static classname *instance = nullptr;
实例访问点的全局性通过静态方法 Instance
实现。
(2) 多线程安全
由于实例指针的动态内存分配放到了访问点中,即延迟加载,所以这是一种懒汉式单例实现。其实现方式的多线程安全性由 std::once_flag
和std::call_once
保证,两者都是 C++11 定义于 <mutex>
中的新特性,配合使用可以确保多线程场景下可调用对象的唯一执行。
std::once_flag
是std::call_once
的辅助结构体,在 GNU 中的实现如下:
struct once_flag
{
private:
typedef __gthread_once_t __native_type;
__native_type _M_once = __GTHREAD_ONCE_INIT;
public:
/// Constructor
constexpr once_flag() noexcept = default;
/// Deleted copy constructor
once_flag(const once_flag&) = delete;
/// Deleted assignment operator
once_flag& operator=(const once_flag&) = delete;
template<typename _Callable, typename... _Args>
friend void
call_once(once_flag& __once, _Callable&& __f, _Args&&... __args);
};
可以看到, call_once
被声明为 once_flag
的友元函数,为的是 call_once
可以修改 once_flag
中的 _M_once
成员(可调用对象的调用状态)。
std::call_once
是一个可变参数模板函数,其声明如下:
template<typename _Callable, typename… _Args> void
call_once(once_flag& __once, _Callable&& __f, _Args&&… __args);
可变参数经完美转发传入可调用对象,具体到 Apollo 中,可调用对象指的是为实例指针分配动态内存的 lambda 表达式:
[&] { instance = new (std::nothrow) classname(); }
std::call_once
通过间接调用 pthread_once
函数来确保传入的可调用对象即使在多线程场景下也只能被执行一次,pthread_once
的底层实现基于互斥锁和条件变量,此处不再展开。
// 执行内存分配,当下次再次执行到这个语句时,就不会再去执行了,因为flag被执行过一次了
static std::once_flag flag;
std::call_once(flag, [&] { instance = new (std::nothrow) classname(); });
(3) 防止私自创建实例
这一点通过私有化默认构造函数和另一个用于禁止拷贝构造和拷贝赋值的宏定义实现:
#define DISALLOW_COPY_AND_ASSIGN(classname) \
classname(const classname &) = delete; \
classname &operator=(const classname &) = delete;
到此为止已经足够将任意一个类泛化为单例类了。
CleanUp 方法
此处我们再稍微分析下 CleanUp
静态方法,该方法允许用户调用时执行一些自定义的清理工作(可选):
static void CleanUp() {
auto instance = Instance(false);
if (instance != nullptr) {
CallShutdown(instance);
}
}
调用 CleanUp
方法时,若发现实例指针不为空,则会调用 CallShutdown
模板函数, CallShutdown
模板函数包含两个经类型萃取(type traits)进行重载的实现:
template <typename T>
typename std::enable_if<HasShutdown<T>::value>::type CallShutdown(T *instance) {
instance->Shutdown();
}
template <typename T>
typename std::enable_if<!HasShutdown<T>::value>::type CallShutdown(
T *instance) {
(void)instance;
}
HasShutdown::value的内容参考
百度apollo源码学习(一) DEFINE_TYPE_TRAIT的理解
举例
将apollo中实现泛化单例模式的宏代码放到一个文件中,并编写main.cpp。
class SingletonA
{
private:
~SingletonA() = default;
private:
static int num;
public:
static void get_num() {
std::cout << "\n number of instances of SingletonA: " << num << std::endl;
}
DECLARE_SINGLETON(SingletonA)
};
int SingletonA::num = 0;
SingletonA::SingletonA() {
++num;
}
class SingletonB
{
private:
~SingletonB() = default;
private:
static int num;
public:
void shutdown() {
auto instance = Instance(false);
if (instance != nullptr) {
delete instance;
num = 0;
}
std::cout << "\n SingletonB::Shutdown method was called." << std::endl;
}
static void get_num() {
std::cout << "\n number of instances of SingletonB: " << num << std::endl;
}
DECLARE_SINGLETON(SingletonB)
};
int SingletonB::num = 0;
SingletonB::SingletonB() {
++num;
}
template <typename T>
void ThreadFunc()
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
T *p = T::Instance();
}
int main()
{
std::thread tA1(ThreadFunc<SingletonA>);
std::thread tA2(ThreadFunc<SingletonA>);
std::thread tB1(ThreadFunc<SingletonB>);
std::thread tB2(ThreadFunc<SingletonB>);
tA1.join();
tA2.join();
tB1.join();
tB2.join();
SingletonA::GetNum();
SingletonB::GetNum();
SingletonA::CleanUp();
SingletonB::CleanUp();
SingletonA::GetNum();
SingletonB::GetNum();
return 0;
}