要实现 Signal-Slot,Signal 类中应该拥有一个保存 std::function 的数组:

    template<class FuncType>
class Signal
{
public:
std::vector<std::function<FuncType>> functionals;
};

  接下来将会按照下图中可能出现的问题设计 Signal-Slot:

  1、当对象 A 被摧毁时,funcA 应该自动从 vector 中移除。

  

  要实现自动管理操作,最好的方式是使用 C++ 的智能指针进行管理。智能指针作为一个单纯的变量,当智能指针为对象一个成员变量时,它的生命周期和对象一样,在对象被摧毁的同时智能指针也会自动销毁掉。因此需要一个 SlotImpl 类对 std::function 进行管理:

    template<class FuncType>
class SlotImpl
{
public:
~SlotImpl()
{
// 从 signal 对象的数组中移除
} Signal* signal;
std::function<FuncType> function;
};

  当 SlotImpl 被摧毁时,会调用析构函数将 function 从数组中移除。那么什么时候摧毁 SlotImpl 呢?前面已经说过,SlotImpl 的生命周期由智能指针来管理。使用一个类 Slot,封装该智能指针:

    template<class FuncType>
class Slot
{
public:
std::shared_ptr<SlotImpl<FuncType>> slot;
};

  然后将 Slot 作为类 A 的成员属性:

    class A
{
public:
void funcA() {} Slot<void(void)> slot;
};

  所以,当对象 A 被摧毁时,Slot 对象被摧毁,由于智能指针的关系,SlotImpl 对象也会被销毁,最后 SlotImpl 的析构函数中 funcA 从数组中移除。

  其中有一个小问题,就是对象 signalA 也应该拥有 SlotImpl。SlotImpl 以什么形式保存在 signalA 中时,才能确保 SlotImpl 的生命周期是由 Slot 的智能指针 std::share_ptr 管理,而不是 signalA?

  signalA 要维护对象 SlotImpl 的指针(从而进行回调操作),但绝不允许指染对象的生命周期。根据上面这句话,应该想到智能指针 std::weak_ptr(weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它指向一个由 shared_ptr 管理的对象而不影响所指对象的生命周期,也就是将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。不论是否有 weak_ptr 指向,一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放)。

  Signal 类的设计更改为:

    template<class FuncType>
class Signal
{
public:
std::vector<std::weak_ptr<SlotImpl<FuncType>>> slot;
};

  解决完第一个问题,还有第二个问题。

  2、发生赋值操作 signalB = signalA 是,signalA 和 signalB 应该指向同一个数组。

  为什么要指向同一个数组?因为当对象 A 被摧毁时,funcA 要从所有 Signal 对象中移除。显然赋值操作后,signalA 和 signalB 都拥有 funcA。如果 signalB 的数组只是 signalA 数组的拷贝,当 A 被摧毁后(因为保存类 signalA 的对象指针,很定会从 signalA 的数组中移除),signalB 发生回调操作时,会调用一个不存在的函数,最后报错。

  解决的方法也是使用智能指针,和上面 Slot 的一样,使用类 SignalImpl:

    template<class FuncType>
class SignalImpl
{
public:
std::weak_ptr<SlotImpl<FuncType>> slot;
}; //-------------------------------------------------------
template<class FuncType>
class Signal
{
public:
std::shared_ptr<SignalImpl<FuncType>> impl;
};

  使用 std::share_ptr,发生赋值操作后,signalA 和 signalB 都指向同一个 SignalImpl。

  重点部分都已经介绍完,下面给出完整代码:

  Signal.h

#pragma once
#include <functional>
#include <memory>
#include <vector> namespace Simple2D
{
//---------------------------------------------------------------------
// bind_member
//---------------------------------------------------------------------
template<class Return, class Type, class... Args>
std::function<Return(Args...)> bind_member(Type* instance, Return(Type::*method)(Args...))
{
/* 匿名函数 */
return[=] (Args&&... args) -> Return
{
/* 完美转发:能过将参数按原来的类型转发到另一个函数中 */
/* 通过完美转发将参数传递给被调用的函数 */
return (instance->*method)(std::forward<Args>(args)...);
};
} //---------------------------------------------------------------------
// SignalImpl
//---------------------------------------------------------------------
template<class SlotImplType>
class SignalImpl
{
public:
std::vector<std::weak_ptr<SlotImplType>> slots;
}; //---------------------------------------------------------------------
// SlotImpl
//---------------------------------------------------------------------
class SlotImpl
{
public:
SlotImpl() {} virtual ~SlotImpl() {} /* 将该函数定义成已删除的函数,任何试图调用它的行为将产生编译期错误,是 C++11 标准的内容 */
SlotImpl(const SlotImpl&) = delete; /* 将该函数定义成已删除的函数,任何试图调用它的行为将产生编译期错误,是 C++11 标准的内容 */
SlotImpl& operator= (const SlotImpl&) = delete;
}; //---------------------------------------------------------------------
// SlotImplT
//---------------------------------------------------------------------
template<class FuncType>
class SlotImplT : public SlotImpl
{
public:
SlotImplT(const std::weak_ptr<SignalImpl<SlotImplT>>& signal, const std::function<FuncType>& callback)
: signal(signal)
, callback(callback)
{
} ~SlotImplT()
{
std::shared_ptr<SignalImpl<SlotImplT>> sig = signal.lock();
if ( sig == nullptr ) return; for ( auto it = sig->slots.begin(); it != sig->slots.end(); ++it ) {
if ( it->expired() || it->lock().get() == this ) {
it = sig->slots.erase(it);
if ( it == sig->slots.end() ) {
break;
}
}
}
} std::weak_ptr<SignalImpl<SlotImplT>> signal;
std::function<FuncType> callback;
}; //---------------------------------------------------------------------
// Slot
//---------------------------------------------------------------------
class Slot
{
public:
Slot() {} ~Slot() {} template<class T>
explicit Slot(T impl) : impl(impl) {} operator bool() const
{
return static_cast< bool >(impl);
} private:
std::shared_ptr<SlotImpl> impl;
}; //---------------------------------------------------------------------
// Signal
//---------------------------------------------------------------------
template<class FuncType>
class Signal
{
public:
Signal() : impl(std::make_shared<SignalImpl<SlotImplT<FuncType>>>()) {} template<class... Args>
void operator()(Args&&... args)
{
std::vector<std::weak_ptr<SlotImplT<FuncType>>> slotVector = impl->slots;
for ( std::weak_ptr<SlotImplT<FuncType>>& weak_slot : slotVector )
{
std::shared_ptr<SlotImplT<FuncType>> slot = weak_slot.lock();
if ( slot ) {
slot->callback(std::forward<Args>(args)...);
}
}
} Slot connect(const std::function<FuncType>& func)
{
std::shared_ptr<SlotImplT<FuncType>> slotImpl = std::make_shared<SlotImplT<FuncType>>(impl, func); /* 由于 SignalImpl 使用的是 std::weak_ptr,push_back 操作不会增加引用计数。
因此,如果调用函数 connect 后的返回值没有赋值给 Slot 对象,过了这个函数的
作用域 slotImpl 对象就会被释放掉 */
impl->slots.push_back(slotImpl); return Slot(slotImpl);
} template<class InstanceType, class MemberFuncType>
Slot connect(InstanceType instance, MemberFuncType func)
{
return connect(bind_member(instance, func));
} private:
std::shared_ptr<SignalImpl<SlotImplT<FuncType>>> impl;
};
}

  在 Signal 类中对操作符 () 进行重载,使用了可变参模板,使用完美转发将参数传递到回调函数中。

  

  下面有几点注意的问题:

  1、SlotImplT 保存 SignalImpl 对象指针时使用了弱引用智能指针 std::weak_ptr,因为 SlotImplT 维护指针只是为了将 std::function 从 Signal 数组中移除,而不会指染 Signal 的生命周期。

  2、要访问 std::weak_ptr 时,使用函数 lock 返回一个临时的 std::share_ptr。

  总结:这个 Signal-Slot 是在 ClanLib 游戏引擎的源码中的,并非我原创。只是以如何编写 Signal-Slot 的思路对源码进行解析。虽然只有 100 多行代码,但其中包含了许多的 C++11 的特性。

使用 C++11 编写类似 QT 的信号槽——下篇的更多相关文章

  1. 使用 C++11 编写类似 QT 的信号槽——上篇

    了解 QT 的应该知道,QT 有一个信号槽 Singla-Slot 这样的东西.信号槽是 QT 的核心机制,用来替代函数指针,将不相关的对象绑定在一起,实现对象间的通信. 考虑为 Simple2D 添 ...

  2. C++11实现Qt的信号槽机制

    概述 Qt的信号槽机制是Qt的核心机制,按钮点击的响应.线程间通信等都是通过信号槽来实现的,boost里也有信号槽,但和Qt提供的使用接口很不一样,本文主要是用C++11来实现一个简单的信号槽,该信号 ...

  3. Qt学习记录--02 Qt的信号槽机制介绍(含Qt5与Qt4的差异对比)

    一 闲谈: 熟悉Window下编程的小伙伴们,对其消息机制并不陌生, 话说:一切皆消息.它可以很方便实现不同窗体之间的通信,然而MFC库将很多底层的消息都屏蔽了,尽管使用户更加方便.简易地处理消息,但 ...

  4. 非Qt工程使用Qt的信号槽机制

    非Qt工程,使用Qt的信号槽机制,蛋疼不?反正我现在就是要做这样一件蛋疼的事. 要使用Qt的信号槽机制,下面是从Qt Assist里面关于 signal & slots 的一句介绍: All ...

  5. VJGUI消息设计-兼谈MFC、QT和信号/槽机制

    星期六下午4点,还在公司加班.终于写完了下周要交工的一个程序. 郁闷,今天这几个小时写了有上千行代码吧?虽然大部分都是Ctrl-C+Ctrl-V,但还是郁闷. 作为一个有10年经验的MFC程序员,郁闷 ...

  6. Qt自定义信号槽的使用浅析+实例

    1. Qt中自定义信号槽的使用 Qt框架提供的信号槽在某些特定场景下是无法满足我们的项目需求的,因此我们还设计自己需要的的信号和槽,使用connect()对自定义的信号槽进行连接. 如果想要使用自定义 ...

  7. Qt Connect 信号 槽

    信号和槽机制是 QT 的核心机制 .信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性,也是 QT 区别于其它工具包的重要地方.信号和槽是 QT 自行定义的一种通信机制,它独立于标准 ...

  8. 在非UI线程中更改UI(Delphi使用隐藏窗口来处理,QT使用信号槽)

    在Delphi里我记得是使用TThread.Synchronize(TThreadMethod),原理是利用了一个隐藏窗口来处理. 在QT Debug模式一下,碰到了同样的问题,显示错误: canno ...

  9. Qt的信号槽,一个老MFC的经验

    最近在利用闲暇时间研究Qt,大概有3周了,看过了官网的white paper并浏览了一遍<C++ GUI Programming with Qt 4, 2nd Edition>.总的来说, ...

随机推荐

  1. 转 DataTorrent 1.0每秒处理超过10亿个实时事件

    DataTorrent是一个实时的流式处理和分析平台,它每秒可以处理超过10亿个实时事件. 与Twitter平均每秒大约6000条微博相比,最近发布的DataTorrent 1.0似乎已经超出了需求, ...

  2. Windows中的SID详解

    SID详解前言 SID也就是安全标识符(Security Identifiers),是标识用户.组和计算机帐户的唯一的号码.在第一次创建该帐户时,将给网络上的每一个帐户发布一个唯一的 SID.Wind ...

  3. Windows nginx php cgi-fcgi 配置 xdebug

    之前使用的是 Apache + PHP,不用怎么配置就可以. 由于服务器用的是 nginx,为了和服务器一致,所以本地开发也改为 nginx. 开始只是简单的开启 xdebug, 发现并不行. 找了一 ...

  4. 【转】每天一个linux命令(10):cat 命令

    原文网址:http://www.cnblogs.com/peida/archive/2012/10/30/2746968.html cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内 ...

  5. 使用fiddler进行genymotion安卓虚拟机手机抓包

    1.首先先下载fiddler,这个直接百度就有啦. 2.打开fiddler ,可以看到这个界面还是挺帅的: 3.选择Tools - Fiddler Options -Https选项卡将配置设置为如下: ...

  6. sklearn, Numpy以及Pandas

    pandas里面的对于数据操作比如where,drop以及dropna等都会有一个属性:inplace,这个单词意思是原地,如果inplace=true代表数据本身要执行该操作:如果inplace=f ...

  7. selenium启动谷歌所遇到的问题

    最近在学习selenium webdriver,发现启动火狐时,运行非常慢,几天前一直在尝试启动谷歌驱动启动,但启动中总遇到问题,启动不起来,一直百度查找方法,还是没搞定,个人比较执着,爱钻牛角尖,弄 ...

  8. Zigbee 的 mesh功能设置

    1. 在编译选项中加入ZIGBEEPRO 最大的节点深度为20,网络模式为mesh网络 如果不配置,则为HOME_CONTROLS,支持5级路由深度,每个路由器最多可连接20个节点(最多包括6个路由器 ...

  9. Django 博客项目02 Form验证+ 上传头像(预览)+Ajax用户注册

    头像预览 $("#avatar_file").change(function(){ // 获取上传的文件对象 var file=$(this)[0].files[0]; // 读取 ...

  10. springMVC学习(3)-springMVC和mybatis整合

    一.需求:使用springmvc和mybatis完成商品列表查询. 二.整合思路:springMVC+mybaits的系统架构: 1步):整合dao层 mybatis和spring整合,通过sprin ...