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

  考虑为 Simple2D 添加一个类似的信号槽,实现对象间的通信。当然,功能比较简单,不过对于 Simple2D 就足够了。最终的使用看起来像是这样的:

class A
{
public:void FuncA(int v1, float v2, std::string str){log("A: --%d--%f--%s--", v1, v2, str.c_str());}
};class B
{
public:void FuncB(int v1, float v2, std::string str){log("B: --%d--%f--%s--", v1, v2, str.c_str());}
};

    A objA;B objB;Signal<void(int, float, std::string)> signal;Slot slot1 = signal.connect(&objA, &A::FuncA);Slot slot2 = signal.connect(&objB, &B::FuncB);signal(10, 20, "Signal-Slot test");

  类 A 和 类 B 分别有一个函数(返回类型、参数个数及参数类型一样),然后将 A 对象 objA 的 FuncA 函数和 B 对象 objB 的 FuncB 函数绑定到信号对象 signal 中,通过信号 signal 的调用,实现对 FuncA 和 FuncB 函数的调用。输出窗口的输出内容为:

  

  Signal-Slot 能够实现对象间的解耦,接下来按照上面的代码,用 C++11 的特性编写信号槽。

  信号槽 Signal-Slot

  要实现上面的功能似乎并不困难,核心内容就是对回调函数的使用。

  将需要绑定的对象函数保存到 std::function 中,再把 std::function 保存到信号 Signal 对象中,使用数组保存 std::function 能够实现一个 Signal 对应多个 Slot,最后重载 Signal 的操作符 ()。接下来将围绕上面的步骤实现 Signal-Slot。

  std::function

  std::function(引入头文件 <functional>) 是 C++11 的内容,通过 std::function 对 C++ 中各种可调用实体(普通函数、类成员函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的 std::function 对象。

  如果要将成员函数绑定到 std::function 对象中,可以通过以下的代码实现:

class A
{
public:void FuncA(int v1, float v2, std::string str){log("A: --%d--%f--%s--", v1, v2, str.c_str());}
};

  

    std::function<void(int, float, std::string)> Functional;A objA;Functional = std::bind(&A::FuncA, objA, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);Functional(20, 55, "functional test");

  输出结果:

  通过 std::bind 函数类成员函数绑定到 std::function 中,但对于参数要使用占位符 std::placeholders::_x,由于 FuncA 函数有 3 个参数,所以要使用 3 个占位符。

  要实现 Signal-Slot,就要把任意的类成员函数绑定到 std::function 中。对于上面的情况,由于 FuncA 函数有 3 个参数,所以要使用 3 个占位符。对于那些不确定参数个数的类成员函数,如何把它们统一的绑定到 std::function 中呢?或许可以把参数个数为 1 - 10 的常用情况都列举出来,但这样并不是一个号方法。

  类成员函数的函数指针

  在解决将不确定参数个数的类成员函数绑定到 std::function 前,先看一看不用 std::function 实现的类成员函数的回调函数。

class A
{
public:void FuncA(int v1, float v2, std::string str){log("A: --%d--%f--%s--", v1, v2, str.c_str());}
};

    typedef void(A::*Functionl)(int, float, std::string);A objA;Functionl functional = &A::FuncA;A* objAPtr = &objA;(objAPtr->*functional)(20, 55, "functional test");

  输出结果:

  实现的方法和普通函数的函数指针类似,只不过定义函数指针的时候要使用类名 + ::,使用的时候也需要使用对象的指针(这意味着你要多保存一个对象指针)。在不使用 std::function 的情况下,实现类成员函数的回调函数要复杂的多。但有一个好处,就是绑定时和函数参数的个数无关。

  结合上面两种方式的类成员函数的回调,就可以解决那个问题了——将不确定参数个数的类成员函数绑定到 std::function。

  bind_member 类成员绑定函数

  你应该要注意到,无论是 std:: function<void(int, float, std::string)> 的方式,还是 typedef void(Class::*Functional)(int, float, std::string) 的方式,都必须确定函数的返回类型和参数的类型(一旦 std::function 的函数格式确定了,就不能绑定其他格式的函数)。

  下面要编写一个函数 bind_member,功能是将类成员函数(任意返回类型,任意参数类型,任意参数个数)绑定到 std::function 中。它看上去是这样的:

    std::function<void(int, float, std::string)> Functional;A objA;Functional = bind_member(&objA, &A::FuncA);Functional(20, 55, "functional test");

  输出结果:

  上面使用 bind_member 函数的代码中,你可以看出两种方式实现类成员函数回调的影子。那么如何实现 bind_member 呢?由于存在函数返回类型,所以要用到函数模板;由于函数的参数个数和参数类型不同,所以要用到可变参模板;如果你不了解可变参模板,可以看下面关于可变参模板的简单介绍。

  可变参模板

  变参模板是 C++11 的新特性,其基本语法为:

template<class... Args>

  和普通模板不同,添加了三个点...,表示 Args 是模板参数包(template type parameter pack),是一连串任意的参数打成的一个包。下面举一个例子(定义一个函数,接受任意参数并输出)说明如何使用可变参模板:

        template<class... Args>void Log(Args... args){printf("");}

  调用函数 Log 时,传入 1、2、3、4 四个参数:

Log(1, 2, 3, 4);

  虽然定义了一个可变参模板的函数 Log,但内部如何实现才能输出 1, 2, 3, 4  呢?也就是如何获取参数包中的参数,如果能分别获取参数包中的参数就能使用函数 printf 输出了。

  这个是参数包的展开问题,可以使用递归函数的方法展开参数包。因此,需要两个重载函数实现参数包的展开:

        template<class T, class... Args>void Log(T header, Args... args){printf("--%d--\n", header);Log(args...);}void Log(int value){printf("--%d--\n", value);}

  第二个函数可以理解,但是第一个函数是什么意思?这个先不理它,看下面的函数调用:

        Log(1);                // 1Log(1, 2);             // 2Log(1, 2, 3);          // 3Log(1, 2, 3, 4);       // 4

   1、当传入的参数只有 1 时,毫无疑问会调用第二个函数,将 1 输出。

  2、当传入的参数为 1 和 2 时,可以猜测它会调用第一个函数:1 给 header 变量,然后输出。剩下的 2 给 args,由于 args 只有一个参数 2,所以接下来的 Log(args...) 会调用第二个函数输出 2。参数包的展开结束。

  3、当传入的参数为 1, 2, 3 时,显然它会调用第一个函数:1 给 header 变量,然后输出。剩下的 2, 3 给 args,那么接下来的 Log(args...) 调用的是哪一个函数呢?(第一感觉是 args... 表示着一个变量,应该调用第二个函数才对,因为第二个函数接收一个参数,但这样就不能展开接下来的 2 和 3 了)如果能理解这一步,就能理解如何展开参数包了。答案是 args... 会被拆成两部分,第一个参数 2 为一部分,剩下的 3 作为另一部分。既然分成了两部分,它会调用第一个函数处理(第一次接触变参模板的人,很容易把第一个函数 Log 理解成结束两个参数的函数,但并不是)。 接下来的展开和步骤 2 的只有参数 1 和 2 时一样,所以递归展开参数包结束。

  4、当传入的参数为 1, 2, 3, 4 时,这次用图片来说明:

  bind_member 实现

  结合以上的内容,你可以实现 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)...);};}

  代码中只是利用了可变参模板的参数包,解决了函数参数类型和参数个数不确定的问题。然后将函数指针的调用封装在一个匿名函数中,再绑定到 std::function 中。其中使用了 C++11 的完美转发,上面也做了简单的介绍。

  避免文章过长,分成两部分来实现 Signal-Slot,重点部分下篇文章再说。

转载于:https://www.cnblogs.com/ForEmail5/p/7136687.html

使用 C++11 编写类似 QT 的信号槽——上篇相关推荐

  1. Qt的信号槽机制介绍(含Qt5与Qt4的差异对比)

    转载地址: https://blog.csdn.net/nicai888/article/details/51169520 一 闲谈: 熟悉Window下编程的小伙伴们,对其消息机制并不陌生, 话说: ...

  2. 学习QT之信号槽机制详解

    学习QT之信号槽机制详解 一.Qt信号槽机制 概念:信号槽是Qt框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如:按钮检测到自己被点击了一下,它就会发出一个信号(sig ...

  3. python简单消息总线实现,类似于C++ Qt的信号槽

    一. 概述 为了模块间解耦,消息总线是常用的方式. 在其它文章中分别提到了lua和C++语言的消息总线的实现 lua语言的消息总线的实现:lua简单消息总线实现,类似于C++ Qt的信号槽 cpp语言 ...

  4. QT Core | 信号槽03 - 自定义信号与槽

    文章目录 一.前言 二.新建一个QT控制台项目 2.1.New File or Project 2.2.Project Location 2.3.Define Build System 2.4.Kit ...

  5. qt 关闭窗口的槽函数_勇哥的VC++应用框架学习之QT(1) 信号槽、按钮控件、opencv读取显示图片...

    前言勇哥对于C语言,C++早些年有一些接触,这个系列贴子就记载一下C++应用框架的学习经验. 在写程序时,UI.基础类库.应用程序框架对于vc来讲,只能依靠MFC和QT了. 勇哥对MFC有很强的抵触, ...

  6. Qt的信号槽机制介绍

    Qt 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 Qt ...

  7. Boost::signals2 类QT的信号槽实现机制

    signals2 基于Boost里的另一个库signals,实现了线程安全的观察者模式.它是一种函数回调机制,当一个信号关联了多个槽时,信号发出,这些槽将会被调用.google的base库里用的多的模 ...

  8. QT中信号槽的概念及使用

    文章目录 信号槽的概念 函数原型 信号槽连接的三种方式 方式一 方式二 方式三 参数传递 全局参数 信号槽传参 信号槽的对应关系 总结 信号槽的概念 信号函数与槽函数是 Qt 在 C++ 的基础上新增 ...

  9. qt 回调函数设置界面_回调函数实现类似QT中信号机制(最简单)

    1. 定义回调接口类: class UIcallBack { public: virtual void onAppActivated() = 0; virtual void onShowMore() ...

  10. QT Core | 信号槽01 - GUI上按钮触发应用程序里某个类的函数

    文章目录 一.前言 二.新建一个QT项目 2.1.New File or Project 2.2.Location 2.3.Kits 2.4.Details 2.5.汇总 2.6.项目文件 2.7.p ...

最新文章

  1. end-to-end 的神经网络
  2. 哈希分布与一致性哈希算法简介
  3. C - 3 求正弦值
  4. 阿里云部署Docker(5)----管理和公布您的镜像
  5. REVERSE-PRACTICE-BUUCTF-6
  6. cms核心功能_如何根据这些重要功能选择合适的CMS
  7. Arm公布2019年物联网领域五大预测:智能家居、智慧城市、医疗保健上榜
  8. Springboot整合log4j2日志全解
  9. 优秀代码所具备的5大品质 你的代码呢?
  10. JavaScript对象相关及json总结(附实例)
  11. Kotlin教程(五)类型
  12. Typora自动生成标题编号(包含从二级标题开始的)
  13. Java中获取时间戳三种方式
  14. 蓝牙地址BD_ADDR组成
  15. ipad怎样和计算机连接网络,ipad怎样连接电脑itunes
  16. 酒水知识(六大基酒之威士忌_Whisky)
  17. python入门教材 52pj_Micropython入门实操心得
  18. 部署到gcp_肿瘤内科成功举办基础研究及GCP云端学术论坛
  19. 不用下载软件也能打开EPUB?详解这款支持网页端的阅读器
  20. Mysterious Organization

热门文章

  1. 21天实战人工智能系列:人工智能产品经理最佳实践(3)
  2. MySQL的约束、事务、字符串、日期、数学相关及其他补充
  3. es6(var,let,const,set,map,Array.from())
  4. 面向对象chapter2
  5. 【推荐】一个移动开发的网站
  6. typedef用法(二)
  7. 基于 WebGL 的 HTML5 3D 工控隧道可视化系统
  8. web前端炫酷特效-CSS3制作环形星星发光动画
  9. 雅虎扫描用户电子邮件,欧盟不干了:侵犯隐私!
  10. Springboot集成通用Mapper与Pagehelper,实现mybatis+Druid的多数据源配置