首页 > 其他分享 >Muduo库之WeakCallback、Singleton

Muduo库之WeakCallback、Singleton

时间:2022-10-14 14:14:00浏览次数:72  
标签:Singleton Muduo 函数 Signleton value instance static 单例 WeakCallback

WeakCallback

WeakCallback.h 文件中定义了模板类 WeakCallback,在其模板参数中,有一个可变模板参数 ARGS,用以指示回调函数的参数。

在类内部,定义有两个成员变量,分别是 object_function_

成员变量 object_ 是一个弱指针类型,即 weak_ptr。其目的是为了实现一种弱回调机制,使用 weak_ptr 将其绑定到回调函数中,这样对象的生命周期就不会被延长。同时,在调用回调函数时,尝试将其提升为 shared_ptr,如果提升成功,那么说明该回调函数还健在,那么就执行回调;如果提升失败,则无法调用回调函数。这样就可以设计出线程安全的回调函数。

另一个成员变量 function_ 是一个不定参的函数对象,即 std::function。在 C++11 之前,我们需要使用函数指针来定义回调函数,而在 std::function 关键字出现后,我们就可以使用这种更为方便的方式来定义回调函数。它是一个函数包装器模板,最早来自于 boost 库。它可以指向任何函数、函数指针、成员函数、静态函数、lambda 表达式和函数对象均可。

在类成员的内部,重载了 () 运算符:

void operator()(ARGS&&... args) const {
  std::shared_ptr<CLASS> ptr(object_.lock());
  if (ptr) {
    function_(ptr.get(), std::forward<ARGS>(args)...);
  }
}

其中,调用 object_ 变量的 lock() 方法尝试将其提升为强指针 shared_ptr,然后检查其有效性,判断该回调函数是否还健在,如果健在,则调用该回调函数,同时使用 std::forward 进行完美转发。完美转发是指在函数模板中,完全按照模板的参数的类型,即保持参数的左值、右值特征,将参数传递给函数模板中的另一个函数。

在类外,则定义了供常量对象和非常量对象使用的 makeWeakCallback() 函数,以返回 WeakCallback 对象。

Singleton

单例模式的实现

Singleton.h 主要实现了单例模式相关类。常见单例模式的实现主要分为以下几种:

懒汉式

懒汉式要求先声明单例对象,然后在调用时才完成实例化操作。

class Signleton {
public:
  static Signleton* getInstance() {
    if (instance == nullptr) {
      instance = new Signleton();
    }
    return instance;
  }

  static void destory() {
    if (instance != nullptr) {
      delete instance;
      instance = nullptr;
    }
  }
private:
  Signleton() {}
  static Signleton* instance;
};

Signleton* Signleton::instance = nullptr;

这种写法并没有考虑到多线程的情况,因此在多线程情况下可能产生多个实例对象,违背单例原则。

双检锁

为了解决 “懒汉式” 中存在的多线程问题,我们可以通过互斥锁来避免多个实例的创建。

class Signleton {
public:
  static Signleton* getInstance() {
    if (instance == nullptr) {
      mutex.lock();
      if(instance == nullptr) {
      	instance = new Signleton();
      }
      mutex.unlock();
    }
    return instance;
  }

  static void destory() {
    if (instance != nullptr) {
      delete instance;
      instance = nullptr;
    }
  }
private:
  Signleton() {}
  static Signleton* instance;
  static pthread_mutex_t mutex;
};

Signleton* Signleton::instance = nullptr;
pthread_mutex_t Signleton::mutex = PTHREAD_MUTEX_INITIALIZER;

进行两次判断以避免多次加锁和解锁操作,保证线程安全。这两次判空的意义如下:

  • 第一层判空是为了提高效率,即当有一个线程 new 出来对象后,第二个线程就不用竞争第一个线程的对象锁而进行等待;
  • 第二层判空是为了保证线程安全,防止多次实例化操作;

但是,如果该单例对象比较大,那么加锁操作就会成为一个性能瓶颈。

饿汉式

为了解决双检锁所存在的性能瓶颈问题,设计出了 “饿汉式” 的单例模式。饿汉式则要求单例对象的声明和实例化同时完成。

class Signleton {
public:
  static Signleton* getInstance() {
    return instance;
  }

  static void destory() {
    if (instance != nullptr) {
      delete instance;
      instance = nullptr;
    }
  }
private:
  Signleton() {}
  static Signleton* instance;
};

Signleton* Signleton::instance = new Signleton();

因为单例对象的静态初始化是在程序开始之前,在静态资源区中已经初始化了实例对象,所以静态初始化也就保证了线程安全性。在性能要求较高时,可以采用这种方式,从而避免了频繁的加锁、解锁操作造成的资源浪费。

静态内部类

但是如果单例对象无需考虑销毁操作,单例对象的生命周期伴随着整个程序的生命周期,程序结束时,由操作系统自动回收资源。那么如果无需考虑销毁操作,则可以用静态内部类的方式进行实现:

class Signleton {
public:
  static Signleton* getInstance() {
    static Signleton instance;
    return &instance;
  }
private:
  Signleton() {}
};

Singleton实现

muduo 的实现中,模板类 Singleton 的内部定义有两个静态成员变量:ponce_value_。其中,前者是 pthread_once_t 类型,以保证单例且线程安全。后者则是一个指针类型,指向单例对象。它们的初始化操作如下:

template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;
template<typename T>
T* Singleton<T>::value_ = NULL;

类中的获取实例及初始化函数如下:

static T& instance() {
  pthread_once(&ponce_, &Singleton::init);
  assert(value_ != NULL);
  return *value_;
}

static void init() {
  value_ = new T();
  if (!detail::has_no_destroy<T>::value){
    ::atexit(destroy);
  }
}

其中,pthread_once() 保证 init() 函数只调用一次,避免多线程竞争,保证了线程安全。而 has_no_destroy<T> 则是为了判断该类型是否含有 no_destory() 函数,如果不存在,则调用 atexit() 注册 destroy() 函数,当程序正常终结时,调用指定的 destroy() 函数以回收资源。

其中的 has_no_destory<T> 定义如下,这是利用了 C++ 中的 SFINEA(Substitution failure is not an error) 机制,即 “匹配失败不是错误”。具体来说,就是当重载的模板参数展开时,如果展开导致一些类型不匹配,编译器并不报错。而正好可以利用该机制来判断类是否存在某个成员函数。

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;
};

SFINEA 机制中,编译期会优先匹配最合适的函数,匹配失败后会找寻次一级的匹配函数,直到无法匹配到任何函数。而在如上代码中,调用静态变量 value 即可返回是否存在 no_destory() 函数的结果,而 value 的值则取决于 sizeof(test<T>(0)) == 1 这个表达式。

假如类中存在 no_destory() 函数,那么 decltype(&C::no_destory) 表达式会返回一个函数指针,该指针指向类中的 no_destory() 函数。此时 test<T>(0) 就会匹配为 char test() 函数,并返回 char,由于 sizeof(char) 为 1,所以 sizeof(test<T>(0) == 1 表达式会返回 true,表示存在该函数。

假如类中不存在该函数,那么在匹配函数 char test() 时就会匹配错误,进而选择次一级的匹配选项,即 int32_t test(...) 函数,由于该函数参数中为可变参数,所以可以接受任意类型的函数参数,test<T>(0) 与该函数匹配成功,进而返回 int32_t。最后由于 sizeof(int32_t) == 1 返回 false,则表示不存在该函数。

该类中的 destory() 函数如下:

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;
}

其中使用 typedef 关键字定义了一个数组类型,用于在编译期判断类型 T 是否是不完全类型,不完全类型指的是只有声明却没有定义的类,那么不完全类型在 delete 操作时也就无法调用析构函数,因此在 delete value_ 操作之前需要判断类型 T 是否为不完全类型。

如果是不完全类型,那么 sizeof(T) == 0 为真,数组大小为 -1,编译错误;如果是完全类型,那么 sizeof(T) == 0 为假,数组大小为 1,编译成功。

标签:Singleton,Muduo,函数,Signleton,value,instance,static,单例,WeakCallback
From: https://www.cnblogs.com/tuilk/p/16791415.html

相关文章

  • Muduo库之StringPiece、Time
    StringPiece在StringPiece.h文件中,声明了两个类类型,一个是StringArg,另一个是StringPiece,前者用于在传递函数参数时同时兼容C风格的字符串(constchar*)和C++风格......
  • Muduo库之ProcessInfo、Exception、FileUtil
    ProcessInfo在ProcessInfo.h文件中,存在一个命名空间ProcessInfo,其中声明了大部分进程需要使用到相关信息的函数,比如进程的PID、UID、EUID,主机名,进程状态,程状态等等。......
  • java中列表 Not showing null elements 列表中去除null 使用 list.removeAll(Collec
    java中列表Notshowingnullelements列表中去除nullNotshowingnullelements有时候看见list的size与结果不一致,例如下面这样导致原因:list集合允许null值,......
  • CSharp: Singleton Pattern in donet core 3
     ///<summary>///单例模式SingletonPattern///geovindu,GeovinDuedit///</summary>publicclassDatabase{///<summary>......
  • 设计模式 -- Singleton(单例模式)
    单例模式(Singleton)保证一个类仅有一个实例,并提供一个该实例的全局访问点。在软件系统中,经常有这样一个特殊的类,必须保证它们在系统中只存在一个示例,才能确保他们的逻辑......
  • 单例模式 Singleton
    “对象性能”模式面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来......
  • 大并发服务器架构(陈硕muduo库源码解析)——源码分析篇
    muduo库分为base和net两个部分,base中是一些与网络无关的内容,net中时网络库的内容。首先看Timestamp类的源代码:我将每节课讲的代码都放在了myMuduoTest中。1.Timestamp......
  • 长文梳理muduo网络库核心代码及优秀编程细节剖析
    前言Muduo库是陈硕个人开发的Tcp网络编程库,支持Reactor模型,想学习网络库的话,也推荐大家阅读陈硕写的《Linux多线程服务端编程:使用muduoC++网络库》。本人前段时间出于个......
  • Csharp: Singleton Patterns
     ///<summary>///SummarydescriptionforSpooler.///geovindu,GeovinDu,涂聚文///单例模式(SingletonPatterns)///</summary>pu......
  • muduo网络模型
    网络模型一个线程一个eventloop多个TCPServer和TCPClient可以共享同一个eventloop文件传输使用TcpConn::send(),send保证数据会发送给对方,send是非阻塞的(即使output......