回调函数中如何调用类中的非静态成员变量或非静态成员函数

2023-11-02

回调函数中调用类中的非静态成员变量或非静态成员函数

【问题1】如何在类中封装回调函数?

【答】:
  a.回调函数只能是全局的或是静态的。
  b.全局函数会破坏类的封装性,故不予采用。
  c.静态函数只能访问类的静态成员,不能访问类中非静态成员。
 
 【问题2】如何让静态函数访问类的非静态成员?
    【解决方案】:

    声明一静态函数a(),将类实例对象指针做为参数传入。如:
  class A()
  {
      static void a(A *); //静态函数
      void b();  //非静态函数 
  }  
  void A::a(A * pThis)
  {
   pThis->b(); //静态函数中调用非静态函数 
  }
     
 【问题3】回调函数中如何访问非静态成员?
  由于回调函数往往有固定定义,并不接受  A * pThis 参数
  如:CALLBACK MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime);
  
        【解决方案1】:本方案当遇到有多个类实例对象时会有问题。原因是pThis指针只能指向一个对象。
  class A()
  {
      static void a(); //静态回调函数
      void b();  //非静态函数 
      static A * pThis;   //静态对象指针
  }  
  
  A * A::pThis=NULL;
  A::A()   //构造函数中将this指针赋给pThis,使得回调函数能通过pThis指针访问本对象
  {
       pThis=this;
  }
  void A::a()
  {
      if (pThis==NULL) return;
      pThis->b(); //回调函数中调用非静态函数 
  }
  
  【解决方案2】:本方案解决多个类实例对象时方案1的问题。用映射表存所有对象地址,每个对象保存自己的ID号。
  typedef CMap<UINT,UINT,A*,A*> CAMap;
  class A()
  {
      static void a(); //静态回调函数
      void b();  //非静态函数 
      int m_ID;  //本对象在列表中的ID号
      static int m_SID;   //静态当前对象ID        (需要时,将m_ID赋值给m_SID以起到调用本对象函数的功能)
      static CAMap m_Map; //静态对象映射表
  }  
  
  CAMap A::m_Map;
  int   A::m_SID=0;
  
 
  A::A()   //构造函数中将this指针赋给pThis,使得回调函数能通过pThis指针访问本对象
  {
      if(m_Map.IsEmpty())
      {
   m_ID=1;
      }
      else
      { 
        m_ID=m_Map.GetCount()+1;
      }
      m_Map.SetAt( m_ID, this );
  }
  void A::a()
  {
      if (m_Map.IsEmpty()) return;
      A * pThis=NULL;
      if(m_Map.Lookup(m_SID,pThis))
      {
   pThis->b(); //回调函数中调用非静态函数 
      };
  }

=================================

 

C++中类成员函数作为回调函数


回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。 

普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。

这样从理论上讲,C++类的成员函数是不能当作回调函数的。但我们在用C++编程时总希望在类内实现其功能,即要保持封装性,如果把回调函数写作普通函数有诸多不便。经过网上搜索和自己研究,发现了几种巧妙的方法,可以使得类成员函数当作回调函数使用。

这里采用Linux C++中线程创建函数pthread_create举例,其原型如下:

  1. int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );  

第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址,即回调函数。
最后一个参数是运行函数的参数。
这里我们只关注第三个参数start_run,它是一个函数指针,指向一个以void*为参数,返回值为void*的函数,这个函数被当作线程的回调函数使用,线程启动后便会执行该函数的代码。
方法一:回调函数为普通函数,但在函数体内执行成员函数
见以下代码:
  1. class MyClass  
  2. {  
  3.     pthread_t TID;  
  4. public:  
  5.     void func()  
  6.     {  
  7.         //子线程执行代码  
  8.     }  
  9.   
  10.     bool startThread()  
  11.     {//启动子线程  
  12.         int ret = pthread_create( &TID , NULL , callback , this );  
  13.         if( ret != 0 )  
  14.             return false;  
  15.         else  
  16.             return true;  
  17.     }  
  18. };  
  19.   
  20. static void* callback( void* arg )  
  21. {//回调函数  
  22.     ((MyClass*)arg)->func();调用成员函数  
  23.     return NULL;  
  24. }  
  25.   
  26. int main()  
  27. {  
  28.     MyClass a;  
  29.     a.startThread();  
  30. }  

类MyClass需要在自己内部开辟一个子线程来执行成员函数func()中的代码,子线程通过调用startThread()成员函数来启动。这里将回调函数callback写在了类外面,传递的参数是一个指向MyClass对象的指针(在pthrad_create()中由第4个参数this指定),回调函数经过强制转换把void*变为MyClass*,然后再调用arg->func()执行子线程的代码。

这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数,以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。缺点是回调函数在类外,影响了封装性,这里把callback()限定为static,防止在其它文件中调用此函数。


方法二:回调函数为类内静态成员函数,在其内部调用成员函数

在方法一上稍作更改,把回调函数搬到类MyClass里,这样就保持了封装性。代码如下:

  1. class MyClass  
  2. {  
  3.     static MyClass* CurMy;//存储回调函数调用的对象  
  4.     static void* callback(void*);//回调函数  
  5.     pthread_t TID;  
  6.     void func()  
  7.     {  
  8.         //子线程执行代码  
  9.     }  
  10.       
  11.     void setCurMy()  
  12.     {//设置当前对象为回调函数调用的对象  
  13.         CurMy = this;  
  14.     }  
  15. public:  
  16.     bool startThread()  
  17.     {//启动子线程  
  18.         setCurMy();  
  19.         int ret = pthread_create( &TID , NULL , MyClass::callback , NULL );  
  20.         if( ret != 0 )  
  21.             return false;  
  22.         else  
  23.             return true;  
  24.     }  
  25. };  
  26. MyClass* MyClass::CurMy = NULL;  
  27. void* MyClass::callback(void*)  
  28. {  
  29.     CurMy->func();  
  30.     return NULL;  
  31. }  
  32.   
  33. int main()  
  34. {  
  35.     MyClass a;  
  36.     a.startThread();  
  37. }  
类MyClass有了1个静态数据成员CurMy和1个静态成员函数callback。CurMy用来存储一个对象的指针,充当方法一中回调函数的参数arg。callback当作回调函数,执行CurMy->func()的代码。每次建立线程前先要调用setCurMy()来让CurMy指向当前自己。

这个方法的好处时封装性得到了很好的保护,MyClass对外只公开一个接口startThread(),子线程代码和回调函数都被设为私有,外界不可见。另外没有占用callback的参数,可以从外界传递参数进来。但每个对象启动子线程前一定要注意先调用setCurMy()让CurMy正确的指向自身,否则将为其它对象开启线程,这样很引发很严重的后果。


方法三:对成员函数进行强制转换,当作回调函数

代码如下:

  1. class MyClass  
  2. {  
  3.     pthread_t TID;  
  4.     void func()  
  5.     {  
  6.         //子线程执行代码  
  7.     }  
  8. public:  
  9.     bool startThread()  
  10.     {//启动子线程  
  11.         typedef void* (*FUNC)(void*);//定义FUNC类型是一个指向函数的指针,该函数参数为void*,返回值为void*  
  12.         FUNC callback = (FUNC)&MyClass::func;//强制转换func()的类型  
  13.         int ret = pthread_create( &TID , NULL , callback , this );  
  14.         if( ret != 0 )  
  15.             return false;  
  16.         else  
  17.             return true;  
  18.     }  
  19. };  
  20.   
  21. int main()  
  22. {  
  23.     MyClass a;  
  24.     a.startThread();  
  25. }  
这个方法是原理是, MyClass::func最终会转化成 void func(MyClass *this); 也就是说在原第一个参数前插入指向对象本身的this指针。 可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数。对编译器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)这两种函数指针虽然看上去很不一样,但他们的最终形式是相同的,因此就可以把成员函数指针强制转换成普通函数的指针来当作回调函数。在建立线程时要把当前对象的指针this当作参数传给回调函数(成员函数func),这样才能知道线程是针对哪个对象建立的。

方法三的封装性比方法二更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

回调函数中如何调用类中的非静态成员变量或非静态成员函数 的相关文章

  • 侯捷系列:c++面向对象高级编程(上)

    文章目录 基于对象的程序设计 不带有指针成员变量的类 以复数类 Complex 为例 头文件的结构 访问级别 函数设计 内联函数 构造函数 常量成员函数 参数的值传递和引用传递 返回值的值传递和引用传递 友元 操作符重载 在类内声明 pub
  • C++知识积累:内存对齐理解

    为什么要进行内存对齐 这是因为CPU的读取总是对齐的 举个例子 假设CPU是32位的 那么CPU每次读取的4字节数据的首地址都是4的倍数 也就是说 内存中数据首地址为4的倍数时 CPU一次操作就可以完成数据读取 假设有一个int型四字节大小
  • IUnknown—COM和MFC

    转自 http hi baidu com zhangqiuxi blog item 6d9603ad9c8fe5084b36d6a0 html 问题 我用MFC编写COM程序有一段时间了 知道如何使用宏和嵌套类 以及如何在嵌套类中处理IUn
  • 调用拷贝构造函数的几种情况(附面试题)

    1 深拷贝和浅拷贝 拷贝构造函数的使用 有时候需要自己定义拷贝构造函数 以避免浅拷贝问题 在什么情况下需要用户自己定义拷贝构造函数 一般情况下 当类中成员有指针变量 类中有动态内存分配时常常需要用户自己定义拷贝构造函数 在什么情况下系统会调
  • C++ 中的虚函数及虚函数表

    C 中的虚函数及虚函数表 一 虚函数及虚函数表的定义 二 虚函数表指针和虚函数表的创建时机 三 虚函数实现多态的原理 一 虚函数及虚函数表的定义 虚函数 虚函数就是在基类中定义一个未实现的函数名 使用虚函数的核心目的就是通过基类访问派生类定
  • 解决“17: 错误:程序中有游离的‘\240’,\302’

    参考链接 https blog csdn net asuphy article details 54602426 执行如下命令即可 sed i s o240 o302 g dy haikang test cpp
  • 编写程序模拟完成动态分区存储管理方式的内存分配和回收。

    usr bin python coding utf 8 class Table object 空闲分区表 0 开始地址 1 长度 freeTable 占用分区表 0 程序名 1 开始地址 2 长度 useTable def init sel
  • c/c++入门教程 - 1.基础c/c++ - 1.0 Visual Studio 2019安装环境搭建

    推荐视频课程 https www bilibili com video BV1et411b73Z p 2 已投币三连 b站果然是个学习的网站 本来是想在linux环境下运行QT 于是先学了几个月linux嵌入式驱动开发 后来发现太底层了 与
  • C/C++中浮点数格式学习——以IEEE75432位单精度为例

    这是浮点数的通常表示形式 在IEEE754中 单精度浮点数有如下形式 32位单精度 单精度二进制小数 使用32个比特存储 1 8 23位长 S Exp Fraction 31 30至23偏正值 实际的指数大小 127 22至0位编号 从右边
  • C/C++ 引用作为函数的返回值

    语法 类型 函数名 形参列表 函数体 特别注意 1 引用作为函数的返回值时 必须在定义函数时在函数名前将 2 用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本 代码来源 RUNOOB include
  • 经典面试题之new和malloc的区别

    new和malloc的区别是C C 一道经典的面试题 我也遇到过几次 回答的都不是很好 今天特意整理了一下 0 属性 new delete是C 关键字 需要编译器支持 malloc free是库函数 需要头文件支持 1 参数 使用new操作
  • 为何在新建STM工程中全局声明两个宏

    在uVision中新建STM32工程后 需要从STM32标准库中拷贝标准外设驱动到自己的工程目录中 此时需要在工程设置 gt C C 选项卡下的Define文本框中键入这两个全局宏定义 STM32F40 41xxx USE STDPERIP
  • floor(),ceil()函数

    地板 天花板函数 均包含在math h中 意思分别为 返回不大于形参的最小整数和不小于形参的最大整数 include
  • C 语言教程:数据类型和格式说明符

    C 语言中的数据类型 C 中的变量必须是指定的 数据类型 并且您必须在 printf 函数中使用 格式说明符 来显示它 创建变量 int myNum 5 整数 没有小数点 float myFloatNum 5 99 浮点数 char myL
  • Java反序列化漏洞-CC1利用链分析

    文章目录 一 前置知识 1 反射 2 Commons Collections是什么 3 环境准备 二 分析利用链 1 Transform
  • C++中的并发多线程网络通讯

    C 中的并发多线程网络通讯 一 引言 C 作为一种高效且功能强大的编程语言 为开发者提供了多种工具来处理多线程和网络通信 多线程编程允许多个任务同时执行 而网络通信则是现代应用程序的基石 本文将深入探讨如何使用C 实现并发多线程网络通信 并
  • C/C++编程中的算法实现技巧与案例分析

    C C 编程语言因其高效 灵活和底层的特性 被广大开发者用于实现各种复杂算法 本文将通过10个具体的算法案例 详细探讨C C 在算法实现中的技巧和应用 一 冒泡排序 Bubble Sort 冒泡排序 Bubble Sort 是一种简单的排序
  • C/C++编程中的算法实现技巧与案例分析

    C C 编程语言因其高效 灵活和底层的特性 被广大开发者用于实现各种复杂算法 本文将通过10个具体的算法案例 详细探讨C C 在算法实现中的技巧和应用 一 冒泡排序 Bubble Sort 冒泡排序 Bubble Sort 是一种简单的排序
  • C++中的引用

    一 引用的概念 引用不是新定义一个变量 而是给已有变量取一个别名 编译器不会为引用变量开辟内存空间 而和它引用的变量共用一块内存空间 注意 由于C 兼容C 所以 既可以是引用符号 也可以是取地址 int a 0 int b a cout l
  • Woocommerce:添加第二个电子邮件地址不起作用,除非收件人是管理员

    我尝试了多种方法来向 Woocommerce 电子邮件添加其他收件人 但它似乎仅适用于主要收件人是管理员的测试订单 这些是我尝试过的片段 如果订单的客户是管理员 则电子邮件将发送到这两个地址 如果订单包含客户电子邮件地址 则仅发送至该电子邮

随机推荐

  • Flask中请求数据的获取和返回响应

    一 表单数据的获取 ussername request form get uname 定义一个变量接收数据 从request form也就是请求表单中获取 二 flask文件的上传 先来了解flask中文件对象的属性 files 文件数据
  • 动手学深度学习——矩阵求导之自动求导

    深度学习框架通过自动计算导数 即自动微分 automatic differentiation 来加快求导 实际中 根据我们设计的模型 系统会构建一个计算图 computational graph 来跟踪计算是哪些数据通过哪些操作组合起来产生
  • 【C++】超详细入门——详解函数返回类型

    目录 无返回值的函数 有返回值的函数 如何返回值 不要返回局部变量或临时量的引用或指针 使用尾置返回类型或decltype 作者简介 即将大四的北京某能源高校学生 座右铭 九层之台 起于垒土 所以学习技术须脚踏实地 这里推荐一款刷题 模拟面
  • YOLOV5通道剪枝【附代码】

    之前的博客中已经实现了YOLOv4 YOLOR YOLOX的剪枝 经过了几天的辛勤努力 终于实现了YOLOv5的剪枝 相关链接如下 YOLOv4剪枝 剪枝相关细节理论这里有写 YOLOv4剪枝 YOLOX剪枝 YOLOX剪枝 YOLOR剪枝
  • GAN实现MNIST

    GAN是生成对抗网络 具体的原理这里就不详解了 该博文简要实现了GAN的搭建 在MNIST数据集上使用 亲测100 可用 导入相应的代码库 import tensorflow as tf import numpy as np import
  • ubuntu系统将磁盘剩余容量扩到文件目录上

    操作系统ubuntu 20 04 6 live server amd64 磁盘现状 lsblk 查看磁盘信息 df h 显示存在的卷组信息 Free PE 还有58G 开始扩容 1 调整命令 参考 1 例如增大至220G lvextend
  • 【华为OD机试2023】找等值元素 C++ Java Python

    华为OD机试2023 找等值元素 C Java Python 前言 如果您在准备华为的面试 期间有想了解的可以私信我 我会尽可能帮您解答 也可以给您一些建议 本文解法非最优解 即非性能最优 不能保证通过率 Tips1 机试为ACM 模式 你
  • 第十二届蓝桥杯国赛

    刚进行完第十二届蓝桥杯国赛 说一下题目感想 这次是四道填空题 六道代码题 感觉这次出的题还比较对路 不像原来很难做出来 但是也有粗心做错的题 算法前面考的到不多 后面大题考的多 动态规划 深搜等 过几天出成绩 希望成绩可以稍微喜人点 第一题
  • Unity UI -- (5)增加基础按钮功能

    分析分析一些常见UI 良好的UI设计会清晰地和用户沟通 用户知道他们能和屏幕上哪些东西交互 哪些不能 如果他们进行了交互 他们也要清楚地知道交互是否成功 换句话说 UI要提供给用户很多反馈 我们可以来看看在Unity里或者在计算机上的任何应
  • “Error running ‘Tomcat 9.0‘: Address localhost:1099 is already in use”报错问题

    Error running Tomcat 9 0 Address localhost 1099 is already in use 报错问题 使用idea运行tomcat时左下方出现红色小方块提示问题 Error running Tomca
  • 系统测试设计的10种方法

    一 等价类划分 等价类的概念 等价类 某个输入域的子集合 在这个集合中 每一个输入条件都是等效 的 如果其中一个输入不能导致问题发生 那么集合中其它输入条件进行测试也不可能发现错误 有效等价类 合理的输入数据 指满足产品规格说明的输入数据
  • swagger接口需要权限验证解决方案

    目录 背景 解决方案 背景 当我们在使用s w a g g e r的情况下 经常会遇到需要授权或者请求带有token才可以访问接口 这里我们就是解决授权问题 解决方案 废话不多说 我们直接给出解决方案 具体代码如下 import org s
  • Linux 线程同步的三种方法

    线程的最大特点是资源的共享性 但资源共享中的同步问题是多线程编程的难点 linux下提供了多种方式来处理线程同步 最常用的是互斥锁 条件变量和信号量 一 互斥锁 mutex 通过锁机制实现线程间的同步 初始化锁 在Linux下 线程的互斥量
  • python小游戏——打砖块代码开源

    作者 小刘在这里 每天分享云计算网络运维课堂笔记 努力不一定有回报 但一定会有收获加油 一起努力 共赴美好人生 夕阳下 是最美的 绽放 愿所有的美好 再疫情结束后如约而至 目录 一 效果呈现 二 主代码 三 cfg 四 README 一 效
  • 【Linux】Linux系统下libevent库的安装

    1 首先进行libevent版本的下载 可以去libevent的官网进行下载 地址为 https libevent org 2 将下载的libevent拖拽到linux系统的桌面 3 在linux中进入到桌面 因为我们将这个库放在了桌面 c
  • Linux系统更改时区

    1 UTC和CST UTC 世界协调时 Universal Time Coordinated的缩写 CST 这个代号缩写 并不是一个统一标准 目前 可以同时代表如下 4 个不同版本的时区概念 要根据上下文语义加以区分 1 China Sta
  • circos-session 1 Lesson 11 Ideogram,Color,Scale,Order and Orientation

    data tracks manipulate what ideogram are displayed their order and orientation Filtering Ideograms with a prefix remove
  • linux常用目录

    原文地址 http os chinaunix net a2007 1227 974 000000974539 shtml vmlinuz 该目录中存放的是系统内核 bin 该目录中存放Linux的常用命令 在有的版本中是一些和根目录下相同的
  • nRF52832 — 连接指定name、UUID、addr的蓝牙设备

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX 作 者 文化人 XX 联系方式 XX 版权声明 原创文章 欢迎评论和转载 转载时能告诉我一声就最好了 XX 要
  • 回调函数中如何调用类中的非静态成员变量或非静态成员函数

    回调函数中调用类中的非静态成员变量或非静态成员函数 问题1 如何在类中封装回调函数 答 a 回调函数只能是全局的或是静态的 b 全局函数会破坏类的封装性 故不予采用 c 静态函数只能访问类的静态成员 不能访问类中非静态成员 问题2 如何让静