百度apollo源码学习(二)apollo中的单例模式


前言

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_flagstd::call_once 保证,两者都是 C++11 定义于 <mutex> 中的新特性,配合使用可以确保多线程场景下可调用对象的唯一执行。

std::once_flagstd::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;
}

参考

深入探索单例设计模式:以百度 Apollo 为例
apollo 7.0——单例设计模式解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖茄子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值