【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 直接传递回调函数:最灵活,支持多种回调类型,适用于通用场景,但有性能开销。