一、std::function与std::bind双剑合璧
因为类成员函数都有一个默认的参数,this,作为第一个参数,这就导致了类成员函数不能直接赋值给std::function
,这时候我们就需要std::bind
了,简言之,std::bind
的作用就是转换函数签名,将缺少的参数补上,将多了的参数去掉,甚至还可以交换原来函数参数的位置。
#include <iostream>
#include <functional>
// 类成员函数
class TestClass
{
public:
int classMemberFun(int a, int b) { return a + b; }
};
int main() {
// 类成员函数(使用std::bind绑定类成员函数)
TestClass testObj;
std::function<int(int,int)> functional = std::bind(&TestClass::classMemberFun, testObj, std::placeholders::_1, std::placeholders::_2);
int ret = functional(10, 50);
std::cout << "类成员函数:" << ret << std::endl;
return 0;
}
二、std::function与std::bind实现函数回调功能
在 C++11 之前,回调函数一般是通过函数指针实现,函数指针的用法非常简单,但是它只能指向全局或静态函数,这有点太不灵活了,而且我们都知道在 C/C++ 中,全局的东西都很可怕,稍有不慎就会被篡改或随便调用。
但幸好,在 C++11 之后,我们多了一种选择:std::function
。std::function
可以说是函数指针的超集,它除了可以指向全局和静态函数,还可以指向仿函数,Lambda 表达式,类成员函数,甚至函数签名不一致的函数,可以说几乎所有可以调用的对象都可以当做std::function
,当然对于后两个需要使用std::bind
进行配合。
当然,任何东西都会有优缺点,std::function
填补了函数指针的灵活性,但会对调用性能有一定损耗,经测试发现,在调用次数达 10 亿次时,函数指针比直接调用要慢 2 秒左右,而std::function
要比函数指针慢 2 秒左右,这么少的损耗如果是对于调用次数并不高的函数,替换成std::function
绝对是划得来的。
下面我们通过一个例子说明std::function
与std::bind
是怎么实现函数回调功能的。
线程类:
#include <iostream>
#include <functional>
using tTask = std::function<void(std::string)>;
// 线程类
class ThreadObject
{
public:
ThreadObject() {}
~ThreadObject() {}
public:
void settask(tTask task)
{
m_task = task;
}
void run()
{
// 回调执行任务函数
m_task("http://172.0.0.1/test.zip");
}
private:
tTask m_task; // std::function类型,调用者,调用回调函数
};
// 下载任务函数,也是回调函数
void downTask(std::string str)
{
std::cout << "download " << str << std::endl;
}
客户端:
#include "ThreadObject.hpp"
// 下载任务函数,也是回调函数
void downTask(std::string str)
{
std::cout << "download " << str << std::endl;
}
int main() {
ThreadObject Threadobj;
Threadobj.settask(std::bind(&downTask, std::placeholders::_1)); // 设置任务函数
Threadobj.run();
return 0;
}
三、扩展:std::bind与std::function模拟实现Qt信号槽
Qt 信号槽实现信号的发送和接收,类似观察者。简单说明:
- sender:发出信号的对象
- signal:发送对象发出的信号
- receiver:接收信号的对象
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
- emit:发送信号
这里准备用std::function
与std::bind
模拟实现 Qt 信号槽。
下面实现第一种:
// 信号对象类
class SignalObject
{
public:
void connect(std::function<void(int)> slotFun)
{
m_callFun = slotFun;
}
void emitSignal(int signalVal)
{
m_callFun(signalVal);
}
private:
std::function<void(int)> m_callFun; // 回调函数,存储槽函数
};
// 槽对象类
class SlotObject
{
public:
SlotObject() {}
public:
void slotMember(int signalVal)
{
std::cout << "signal:" << signalVal << " recv:" << this << std::endl;
}
};
客户端:
int main() {
SignalObject signalObject; // 信号对象
SlotObject slotObject; // 槽对象
std::cout << "slotObject:" << &slotObject << std::endl;
// 连接信号槽(此时m_callFun存储着slotMember函数对象)
signalObject.connect(std::bind(&SlotObject::slotMember, slotObject, std::placeholders::_1));
// 发射信号
signalObject.emitSignal(1);
return 0;
}
输出如下:
slotObject:00D3FDEF
signal:1 recv:00D3FE01
可以发现成功调用了回调函数,并正确接收到了信号,我们的成员函数可以通过回调实现了调用。但是接收者的地址并不是我们定义的 slotobject,即 connect 的是别的对象,具体可以参考开篇链接介绍知,connect 过程发生了拷贝构造。
避免拷贝构造
修改我们的信号类,可以避免拷贝构造:
// 信号对象类2:避免了拷贝构造
class SignalObject2
{
public:
void connect(SlotObject* recver, std::function<void(SlotObject*, int)> slotFun)
{
m_recver = recver; // 保存连接的槽对象
m_callFun = slotFun;
}
void emitSignal(int signal)
{
m_callFun(m_recver, signal);
}
private:
SlotObject* m_recver;
std::function<void(SlotObject*, int)> m_callFun;
};
即我们在 connect 时把 recver 保存起来。
客户端:
int main() {
SignalObject2 signalObject2;
SlotObject slotObject;
std::cout << "slotObject:" << &slotObject << std::endl;
// 连接信号槽
std::function<void(SlotObject*, int)> slot = &SlotObject::slotMember;
signalObject2.connect(&slotObject, slot);
// 发射信号
signalObject2.emitSignal(2);
return 0;
}
输出如下:
slotObject:008FFBD3
signal:2 recv:008FFBD3
sender类实现
当一个槽 slot 和多个信号 signal 连接者,我们并不知道是谁调用的,Qt 中我们知道可以通过 sender() 返回一个 QObject* 来判断,这里模仿实现 sender 方法。
// Object类
class Object
{
public:
Object* self()
{
return this;
}
std::function<Object* (void)> m_sender;
};
// 槽对象类3
class SlotObject3 :public Object
{
public:
SlotObject3() {}
public:
void slotMember(int signal)
{
if (m_sender) {
std::cout << "sender:" << m_sender() << std::endl;
}
std::cout << "signal:" << signal << " recv:" << this << std::endl;
}
};
// 信号对象类3
class SignalObject3 :public Object
{
public:
void connect(SlotObject3* recver, std::function<void(SlotObject3*, int)> slot)
{
m_recver = recver;
m_callFun = slot;
}
void emitSignal(int signal)
{
m_recver->m_sender = std::bind(&SignalObject3::self, this);
m_callFun(m_recver, signal);
m_recver->m_sender = NULL;
}
private:
SlotObject3* m_recver;
std::function<void(SlotObject3*, int)> m_callFun;
};
即定义一个基类 Object 和一个回调变量 sender,在每次发送时绑定上发送者即可。
客户端:
int main() {
SignalObject3 signalObject3;
SlotObject3 slotObject3;
std::cout << "signalObject3:" << &signalObject3 << std::endl;
std::cout << "slotObject3:" << &slotObject3 << std::endl;
// 连接信号槽
std::function<void(SlotObject3*, int)> slot3 = &SlotObject3::slotMember;
signalObject3.connect(&slotObject3, slot3);
// 发射信号
signalObject3.emitSignal(3);
return 0;
}
输出如下:
signalObject3:00DDFC40
slotObject3:00DDFC10
sender:00DDFC40
signal:3 recv:00DDFC10
参考:
C++11 std::function 和 std::bind 实现函数回调功能
通过c++11的std::bind及std::function实现类方法回调,模拟Qt实现信号槽