【c++随笔30】C++ 注册回调函数

【c++随笔30】C++ 注册回调函数

原创作者:郑同学的笔记

原文链接:https://zhengjunxue.blog.csdn.net/article/details/145456783

一、回调函数的基本概念

1、回调函数的定义:

  • 在 C++ 中,回调函数是指一个函数通过参数传递给另一个函数,在特定的时机被调用。
  • 在 C++ 中,回调函数可以是普通的函数,也可以是类的成员函数。

2、回调函数的实现:

具体来说,可以有以下几种方式来实现回调函数:

  • 通过普通函数指针,作为回调
  • 通过成员函数指针,作为回调
  • 通过 std::function 和 std::bind
  • 通过 Lambda 表达式

3、面向对象编程:

在面向对象编程中,我们通常需要注册类的成员函数作为回调。

在这个教程中,我们将讨论如何在 C++ 中注册类成员函数作为回调,并展示不同的实现方式.

二、回调函数的实现(面向对象编程)

1、背景

我们有一个场景,Me(我)和 YuanFang(远方)是两个类。Me 类需要将自己的回调函数提供给 YuanFang 类,以便 YuanFang 在特定时刻调用这些回调函数。我们将分别使用三种不同的方式实现回调函数的注册。

2、代码实现概述:

在 YuanFang 类中,提供了三种注册回调函数的方式,分别是:

  • 方式一:原始指针直接调用------直接传递 Me 类对象的地址
  • 方式二:std::bind绑定成员函数------使用 std::bind 绑定 Me 类成员函数
  • 方式三:使用 std::function 直接传递回调函数

3、方式一:原始指针直接调用------直接传递 Me 类对象的地址

在这种方式下,Me 类的对象地址通过指针传递给 YuanFang 类,然后在 YuanFang 类内直接调用 Me 类的成员函数。这种方式可以直接访问 Me 类实例的所有公共方法和成员。

  • 优点
  • 简单直观:直接通过指针将 Me 类对象传递给 YuanFang 类,简单易懂。
  • 灵活性高:YuanFang 类可以直接调用 Me 类的任何成员函数,具有很高的灵活性。
  • 缺点
  • 耦合性强:YuanFang 类直接依赖于 Me 类实例,这种紧耦合的设计可能影响代码的可扩展性和可维护性。
  • 接受注册的类YuanFang

YuanFang.h

c 复制代码
//YuanFang.h
#pragma once
#include<iostream>
#include<functional>
#include "Me.h"

// 前向声明 Me 类,因为互相引用
class Me;

class YuanFang
{
public:
	YuanFang() {};
	~YuanFang(){};
public:
	void registerCallBack(Me* me);//注册回调函数的接口	
	void kangFengJing();
private:
	Me* m_me;
};

YuanFang.cpp

cpp 复制代码
//YuanFang.cpp
#include "YuanFang.h"
void YuanFang::registerCallBack(Me* me)
{
	std::cout << "远方:收到了你的地址--->并把地址做了保存\n";
	m_me = me;
}

void YuanFang::kangFengJing()
{
	//看完后,
	std::cout << "远方:风景很好--->启动回调函数0\n";
	m_me->say(0);
}
  • 回调函数的类Me

Me.h

c 复制代码
//Me.h
#pragma once
#include "YuanFang.h"
#include<iostream>

// 前向声明 YuanFang 类,因为互相引用
class YuanFang;

class Me
{
public:
	Me();
	~Me();
public:
	void quKanFengJing();
public:
	void say(int num);//say 就是回调函数

private:
	YuanFang* m_yuanFang;
};

Me.cpp

cpp 复制代码
//Me.cpp
#include "Me.h"
#include<functional>
using namespace std;
Me::Me()
{
	m_yuanFang = new YuanFang();
	cout << "我  :我把我的地址注册给你,未来你可以调用我的函数了0。\n";
	m_yuanFang->registerCallBack(this);//注册回调函数(这里其实是把类实例化后的地址注册过去了)
}

Me::~Me()
{
	if(nullptr !=m_yuanFang)
		delete m_yuanFang;
}

void Me::quKanFengJing()
{
	cout << "我  : 替我去看看远方的风景0\n";
	m_yuanFang->kangFengJing();
}

void Me::say(int num)
{
	cout << "我  :远方告诉我风景很好看." << num << endl << endl;
}
  • 测试类
cpp 复制代码
#include <iostream>
#include "Me.h"

int main()
{
    Me me;
    me.quKanFengJing();
}

4、方式二:std::bind绑定成员函数------使用 std::bind 绑定 Me 类成员函数

在这种方式中,YuanFang 类通过 std::bind 绑定 Me 类的成员函数,并将其存储为 std::function 类型的回调函数。std::bind 可以将成员函数与对象实例进行绑定,并生成一个可调用的函数对象。

优点

减少耦合:虽然 YuanFang 仍然需要知道 Me 类的存在,但通过 std::bind 绑定了成员函数,降低了直接的依赖关系,系统的耦合性较低。

灵活性较高:使用 std::bind 可以让 YuanFang 更灵活地处理回调函数,支持绑定不同的成员函数和不同的对象实例。
缺点

代码复杂度增加:虽然通过 std::bind 可以减少耦合,但代码的复杂度相对增加,特别是需要理解 std::bind 的用法。

性能问题:在某些高性能要求的场景下,std::bind 可能会带来额外的性能开销。

  • 接受注册的类YuanFang

YuanFang.h

c 复制代码
//YuanFang.h
#pragma once
#include<iostream>
#include<functional>
#include "Me.h"

// 前向声明 Me 类,因为互相引用
class Me;

class YuanFang
{
public:
	YuanFang() {};
	~YuanFang(){};
public:
	void registerCallBack2(Me* me);//注册回调函数的接口
	void kangFengJing2();
private:
	std::function<void(int)> m_callBack2;
};

YuanFang.cpp

cpp 复制代码
//YuanFang.cpp
#include "YuanFang.h"
void YuanFang::registerCallBack2(Me* me)
{
	std::cout << "远方:收到了你的地址--->并把地址做了绑定\n";
	m_callBack2=std::bind(&Me::say, me, std::placeholders::_1);
}

void YuanFang::kangFengJing2()
{
	//看完后,
	std::cout << "远方:风景很好--->启动回调函数2\n";
	m_callBack2(2);
}
  • 回调函数的类Me

Me.h

c 复制代码
//Me.h
#pragma once
#include "YuanFang.h"
#include<iostream>

// 前向声明 YuanFang 类,因为互相引用
class YuanFang;

class Me
{
public:
	Me();
	~Me();
public:
	void quKanFengJing2();
public:
	void say(int num);//say 就是回调函数

private:
	YuanFang* m_yuanFang;
};

Me.cpp

cpp 复制代码
//Me.cpp
#include "Me.h"
#include<functional>
using namespace std;
Me::Me()
{
	m_yuanFang = new YuanFang();

	cout << "我  :我把我的地址注册给你,未来你可以调用我的函数了2。\n";
	m_yuanFang->registerCallBack2(this);//注册回调函数
}

Me::~Me()
{
	if(nullptr !=m_yuanFang)
		delete m_yuanFang;
}

void Me::quKanFengJing2()
{
	cout << "我  : 替我去看看远方的风景2\n";
	m_yuanFang->kangFengJing2();
}

void Me::say(int num)
{
	cout << "我  :远方告诉我风景很好看." << num << endl << endl;
}
  • 测试类
cpp 复制代码
#include <iostream>
#include "Me.h"

int main()
{
    Me me;
    me.quKanFengJing2();
}

5、 方式三:使用 std::function 直接传递回调函数

这种方式通过 std::function 类型的参数直接传递回调函数。std::function 是一个通用的函数包装器,可以接受普通函数、成员函数、函数指针、Lambda 表达式等各种类型的可调用对象。

优点

最灵活:std::function 是一种非常灵活的回调方式,支持多种类型的回调,包括普通函数、成员函数、Lambda 表达式等。

接口统一:通过 std::function,YuanFang 类可以统一处理不同类型的回调函数,提高代码的复用性和可维护性。
缺点

性能开销:虽然 std::function 提供了最大的灵活性,但它也带来了一些性能开销,特别是在回调函数比较频繁调用时,这种开销可能变得明显。

  • 接受注册的类YuanFang

YuanFang.h

c 复制代码
//YuanFang.h
#pragma once
#include<iostream>
#include<functional>

class YuanFang
{
public:
	YuanFang() {};
	~YuanFang(){};
public:
	void registerCallBack3(std::function<void(int)> func);//注册回调函数的接口
	void kangFengJing3();

private:
	std::function<void(int)> m_callBack3;
};

YuanFang.cpp

cpp 复制代码
//YuanFang.cpp
#include "YuanFang.h"

void YuanFang::registerCallBack3(std::function<void(int)> func)
{
	std::cout << "远方:收到了你的成员函数地址--->并把地址做了绑定\n\n";
	m_callBack3 = func;
}

void YuanFang::kangFengJing3()
{
	//看完后,
	std::cout << "远方:风景很好--->启动回调函数3\n";
	m_callBack3(3);
}
  • 回调函数的类Me

Me.h

c 复制代码
//Me.h
#pragma once
#include "YuanFang.h"
#include<iostream>

class Me
{
public:
	Me();
	~Me();
public:
	void quKanFengJing3();
public:
	void say(int num);//say 就是回调函数
private:
	YuanFang* m_yuanFang;
};

Me.cpp

cpp 复制代码
//Me.cpp
#include<functional>
using namespace std;
Me::Me()
{
	m_yuanFang = new YuanFang();
	cout << "我  :我把我的成员函数地址注册给你,未来你可以调用这个函数了3。\n";
	m_yuanFang->registerCallBack3(std::bind(&Me::say, this, std::placeholders::_1));//注册回调函数(仅仅是类的成员函数)
}

Me::~Me()
{
	if(nullptr !=m_yuanFang)
		delete m_yuanFang;
}

void Me::quKanFengJing3()
{
	cout << "我  : 替我去看看远方的风景3\n";
	m_yuanFang->kangFengJing3();
}

void Me::say(int num)
{
	cout << "我  :远方告诉我风景很好看." << num << endl << endl;
}
  • 测试类
cpp 复制代码
#include <iostream>
#include "Me.h"

int main()
{
    Me me;
    me.quKanFengJing3(3);
}

三、总结

本文分析了三种不同的回调函数注册方式,它们各有优缺点:

  • 直接传递类对象的地址:简单直观,适合于紧耦合的场景,但缺乏灵活性。
  • 使用 std::bind 绑定成员函数:减少了耦合,具有一定灵活性,但代码复杂度增加。
  • 使用 std::function 直接传递回调函数:最灵活,支持多种回调类型,适用于通用场景,但有性能开销。
相关推荐
七夜zippoe1 小时前
启元 Q1 腿部运动学逆解:Python 算站立下蹲关节角
开发语言·python·ai·机器人·腿部运动学
熊猫钓鱼>_>1 小时前
精准碰一碰近场分享与拖拽投送实战:把文件“碰“进另一台设备【鸿蒙心迹】
开发语言·华为·harmonyos·鸿蒙·openharmony
lengxuenong1 小时前
树状数组 · 信息学学习手册2-离散化与逆序对
c++·学习·算法
十二1 小时前
【洛谷题解】P1115 最大子段和(前缀和 + 维护最小前缀)
数据结构·c++·算法·蓝桥杯
CodeGL2 小时前
9、Python基础之IO编程
开发语言·数据库·python
曹牧2 小时前
SVN:失联项目
开发语言
传奇开心果编程2 小时前
【Rust入门练中学】第9课:集合类型
开发语言·rust·编程语言理论·集合类型·通用编程语言
龙孚信息3 小时前
跨境电商独立站的安全盲区:一次 PHP 核心漏洞的影响评估
开发语言·安全·php
牛牛牌蜡笔小新3 小时前
Java-常用类库
java·开发语言