函数对象:可变函数和参数——回调函数——如何取代虚函数
一、可变函数和参数
- Args:可以作为一个整体传给bind,
- 然后把可调用对象的实参一个个绑定,
- 调用不同的函数对象。
可变参数也可以使用va_start、va_arg、va_end、va_list来实现,我们后面单独讲解。此处重点的bind的使用。
1、参数——拷贝语义
#include <iostream>
#include <thread>
#include <functional>
using namespace std;
void show0() {
// 普通函数。
cout << "第0个,show0,长安归故里。\n";
}
void show1(const string& message) {
// 普通函数。
cout << "第1个,show1," << message << endl;
}
struct CC // 类中有普通成员函数。
{
void show2(int bh, const string& message) {
cout << "第" << bh << "个," << message << endl;
}
};
template<typename Fn, typename...Args>
auto show(Fn fn, Args...args)
{
cout << "before show......\n";
auto f = bind(fn, args...);
cout << "after show。\n";
return f;
}
int main()
{
auto f0 = show(show0);
f0();
cout << endl;
auto f1 = show(show1, "长安归故里。");
f1();
cout << endl;
CC cc;
auto f2 = show(&CC::show2, &cc, 2, "长安归故里。");
f2();
//thread t1(show0);
//thread t2(show1,"我是一只傻傻鸟。");
//CC cc;
//thread t3(&CC::show2,&cc, 3,"我是一只傻傻鸟。");
//t1.join();
//t2.join();
//t3.join();
return 0;
}
输出
before show…
after show。
第0个,show0,长安归故里。
before show…
after show。
第1个,show1,长安归故里。
before show…
after show。
第2个,长安归故里。
2、参数——移动语义
#include <iostream>
#include <thread>
#include <functional>
using namespace std;
void show0() {
// 普通函数。
cout << "第0个,show0,长安归故里。\n";
}
void show1(const string& message) {
// 普通函数。
cout << "第1个,show1," << message << endl;
}
struct CC // 类中有普通成员函数。
{
void show2(int bh, const string& message) {
cout << "第" << bh << "个," << message << endl;
}
};
template<typename Fn, typename...Args>
auto show(Fn&& fn, Args&&...args)// -> decltype(bind(forward<Fn>(fn), forward<Args>(args)...)) //c++11需要把此处放开,c++14不需要
{
cout << "before show......\n";
auto f = bind(forward<Fn>(fn), forward<Args>(args)...);
cout << "after show。\n";
return f;
}
int main()
{
auto f0 = show(show0);
f0();
cout << endl;
auto f1 = show(show1, "长安归故里。");
f1();
cout << endl;
CC cc;
auto f2 = show(&CC::show2, &cc, 2, "长安归故里。");
f2();
//thread t1(show0);
//thread t2(show1,"我是一只傻傻鸟。");
//CC cc;
//thread t3(&CC::show2,&cc, 3,"我是一只傻傻鸟。");
//t1.join();
//t2.join();
//t3.join();
return 0;
}
输出
before show…
after show。
第0个,show0,长安归故里。
before show…
after show。
第1个,show1,长安归故里。
before show…
after show。
第2个,长安归故里。
3、std::forward
forward详细的,我们放到左值和右值时讲解
- std::forward —— (C++11)转发一个函数实参
- std::move —— (C++11)获得右值引用
二、回调函数
- 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我们仅仅讲解下把一个类的一个函数注册到另外一个类中,仅仅在有需要时调用。(其实以下代码完全可以使用虚函数来实现,比如NetClient提供虚函数的注册接口,class BB来负责虚函数的具体实现,在此我们不再扩展)
- class NetClient:代表接受消息的客户端
- class BB:代表接受客户端消息,处理业务的类
#include <iostream>
#include <string>
#include <thread> // 线程类头文件。
#include <functional>
using namespace std;
//void show(const string& message) { // 处理业务的普通函数
// cout << "处理数据:" << message << endl;
//}
class BB {
// 处理业务的类
public:
void show(const string& message) {
cout << "处理业务的类:展示——" << message << endl;
}
};
class NetClient
{
function<void(const string&)> m_callback; // 回调函数对象。
public:
// 注册回调函数,回调函数只有一个参数(网络客户端接收到的数据)。
template<typename Fn, typename ...Args>
void register_callback(Fn&& fn, Args&&...args) {
m_callback = bind(forward<Fn>(fn), forward<Args>(args)..., std::placeholders::_1); // 绑定回调函数。
}
void receive() {
// 消费者线程任务函数。
while (true) {
this_thread::sleep_for(chrono::seconds(2)); // 休眠2秒。
// simulated revceive message from net server
string message = "收到消息是:小明在濮阳工作。";
// 处理出队的数据(把数据消费掉)。
if (m_callback)
{
m_callback(message); // 回调函数,把收到的数据传给它。
}
}
}
};
int main()
{
NetClient client;
BB bb;
client.register_callback(&BB::show, &bb); // 把类成员函数BB::show()注册为回调函数。
client.receive();
}
三、如何取代虚函数
C++虚函数在执行过程中会跳转两次(先查找对象的函数表,再次通过该函数表中的地址找到真正的执行地址),这样的话,CPU会跳转两次,而普通函数只跳转一次。
CPU每跳转一次,预取指令要作废很多,所以效率会很低。(百度)
为了管理的方便(基类指针可指向派生类对象和自动析构派生类),保留类之间的继承关系。
1、虚函数
#include <iostream> // 包含头文件。
#include <functional>
using namespace std;
struct Hero {
// 英雄基类
virtual void show() {
cout << "英雄释放了技能。\n"; }
};
struct XS :public Hero {
// 西施派生类
void show() {
cout << "西施释放了技能。\n"; }
};
struct HX :public Hero {
// 韩信派生类
void show() {
cout << "韩信释放了技能。\n"; }
};
int main()
{
// 根据用户选择的英雄,施展技能。
int id = 0; // 英雄的id。
cout << "请输入英雄(1-西施;2-韩信。):";
cin >> id;
// 创建基类指针,将指向派生类对象,用基类指针调用派生类的成员函数。
Hero* ptr = nullptr;
if (id == 1) {
// 1-西施
ptr = new XS;
}
else if (id == 2) {
// 2-韩信
ptr = new HX;
}
if (ptr != nullptr) {
ptr->show(); // 调用子类的成员函数。
delete ptr; // 释放派生类对象。
}
}
输出
请输入英雄(1-西施;2-韩信。):1
西施释放了技能。
2、包装器和绑定器:取代虚函数
- 包装器和绑定器不要求有继承关系
- 如果有继承关系,基类释放会自动调用子类的析构函数,如果没有继承关系,则不会调用
#include <iostream> // 包含头文件。
#include <functional>
using namespace std;
struct Hero {
// 英雄基类
//virtual void show() { cout << "英雄释放了技能。\n"; }
function<void()> m_callback; // 用于绑定子类的成员函数。
// 注册子类成员函数,子类成员函数没有参数。
template<typename Fn, typename ...Args>
void callback(Fn&& fn, Args&&...args) {
m_callback = bind(forward<Fn>(fn), forward<Args>(args)...);
}
void show() {
m_callback(); } // 调用子类的成员函数。
};
struct XS :public Hero {
// 西施派生类
void show() {
cout << "西施释放了技能。\n"; }
};
struct HX :public Hero {
// 韩信派生类
void show() {
cout << "韩信释放了技能。\n"; }
};
int main()
{
// 根据用户选择的英雄,施展技能。
int id = 0; // 英雄的id。
cout << "请输入英雄(1-西施;2-韩信。):";
cin >> id;
// 创建基类指针,将指向派生类对象,用基类指针调用派生类的成员函数。
Hero* ptr = nullptr;
if (id == 1) {
// 1-西施
ptr = new XS;
ptr->callback(&XS::show, static_cast<XS*>(ptr)); // 注册子类成员函数。
}
else if (id == 2) {
// 2-韩信
ptr = new HX;
ptr->callback(&HX::show, static_cast<HX*>(ptr)); // 注册子类成员函数。
}
if (ptr != nullptr) {
ptr->show(); // 调用子类的成员函数。
delete ptr; // 释放派生类对象。
}
}
输出
请输入英雄(1-西施;2-韩信。):1
西施释放了技能。
参考
1、C++ STL 容器库 中文文档
2、STL教程:C++ STL快速入门
3、https://www.apiref.com/cpp-zh/cpp/header.html
4、https://en.cppreference.com/w/cpp/container
5、WIKI教程_C ++标准库_C++ Library - <iterator>
6、哔哩哔哩_系统化学习C++_C++11神器之可调用对象包装器和绑定器