【STL十七】函数对象:可变函数和参数——回调函数——如何取代虚函数

2023-05-16

函数对象:可变函数和参数——回调函数——如何取代虚函数

  • 一、可变函数和参数
    • 1、参数——拷贝语义
    • 2、参数——移动语义
    • 3、std::forward
  • 二、回调函数
  • 三、如何取代虚函数
    • 1、虚函数
    • 2、包装器和绑定器:取代虚函数

一、可变函数和参数

  • 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神器之可调用对象包装器和绑定器

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

【STL十七】函数对象:可变函数和参数——回调函数——如何取代虚函数 的相关文章

  • windows下编译opencv 3.4.0

    为了方便后期的调试 xff0c 自己动手编译opencv3 4 0 xff0c 这样有需要的时候还可以自己修改修改源代码 通常来说 xff0c 编译32位比较简单 xff0c 直接用cmake生成编译的工程就行了 xff0c 但64位就比较
  • opencv添加的新接口clearVec()的实现

    自己编译的opencv xff0c 之前文章有说添加了这个接口 xff0c 也有上传3 3 0版本添加这个接口之后编译好的库 xff0c 但是没有把实现过程展现出来 xff0c 导致有些朋友问我如何实现的 xff0c 今天把这个实现放出来
  • 苏泊尔电饭煲不工作的维修

    本篇文章与其说是维修 xff0c 倒不如说成是 拆 xff0c 因为维修相对容易 xff0c 但想拆开却很艰难 xff0c 大部分的时间都花在了拆的工作上面 老家伙的样子如下 型号为 xff1a CYSB50FC99 100 xff0c 铭
  • 萨克斯吹不响的解决办法

    刚开始吹萨克斯 xff0c 发现总是吹不响 看各种入门的文章 xff0c 很多都强调口型的重要性 xff0c 各文章说得也都差不多 xff0c 我仔细捉摸 xff0c 不断尝试 xff0c 似乎还是不得要领 特别是安装好之后 xff0c 很
  • vs2010制作安装工程

    这里的安装工程 xff0c 是指制作安装包 xff0c 而不是vs2010的安装包 用向导生成一个安装工程 xff0c 通常会直接打开一个文件编辑窗口 xff1a 这个窗口很容易编辑 xff0c 把所有要安装的文件拖到 应用程序文件夹 上
  • windows下编译ffmpeg源代码

    由于工作原因 xff0c 需要使用ffmpeg在windows下进行代码跟踪 于是 xff0c 上网找相关文章 xff0c 搜索出来有很多 xff0c 经过查看 xff0c 其中的一个英文网站是最好的 xff0c 网址 xff1a http
  • 注册控件失败之一:提示0x80040200错误的处理办法

    今天有客户反馈说控件无法注册 xff0c 晕 xff0c 这问题好容易困扰开发者以及客服人员 xff0c 但是环境千差万别 xff0c 很难做到完全自动化 出现的错误号码有很多 xff0c 但相对的0x80040200这个号码出现的概率较其
  • win10+ubuntu23.04双系统安装

    win10 win10先安装好 xff08 确保主板上各个螺丝稳定 xff0c 至少4对螺丝 43 铜柱 xff0c 否则会各种蓝屏 xff09 如果双系统安装失败了 xff0c 连win10都进不去了 xff0c 用原版ISO刻录的U盘或
  • 冷门指标移中平均线和多空指数的完美结合(一定要看)

    注 xff0c 原贴地址 xff1a http blog sina com cn s blog 7f0a6fa50101hyls html 在此谨以记录防止原帖无法打开为忧 冷门指标移中平均线和多空指数的完美结合 一定要看 xff09 20
  • LINUX下安装QT的惨痛经历

    安装QT的惨痛经历 目标 xff1a 2012 4月下旬 xff0c 计划开始在linux上安装QT和ffmpeg xff0c 准备摸索一下视频客户端的开发 以下是安装过程 由于没有额外的电脑 xff0c 所以使用了虚拟机安装 电脑上刚好有
  • Linux下CAN总线速率设置,socketCAN。

    背景 xff1a 飞思卡尔Freescale的ARM9处理器i MX25系列 socketCAN对于在Linux下操作CAN总线非常的快捷方便 xff0c 其配置方法和在Linux下对网卡的配置相似 xff0c 方法如下 xff1a 1 i
  • c++在Linux环境下的套接字Tcp通信例子(demo)

    demo包括服务端和客户端的通信 xff0c 发送端发送格式为先发送长度为5的字符串数据 xff0c 告知对方接下来的数据长度 xff0c 接收端首先接收到消息长度 xff0c 再根据消息长度接受接下来的消息 服务端 xff1a inclu
  • yolo_mark工具的使用

    之前自己编译了一下yolo mark用来标注样本 我编译时yolo mark依赖了opencv3 2 0 当时为了方便直接把yolo mark exe放到编译yolo的文件夹 现在要在其他地方使用 xff0c 就把所有文件整理出来 其中op
  • GStreamer与opencv实现rtsp推流

    文章目录 前言安装库代码总结 前言 最近工作遇到瓶颈了呀 xff01 xff01 xff01 公司分配给我的任务是deepstream部署 xff0c 太难了 xff0c gstreamer语言学的我头皮发麻 xff01 xff01 xff
  • 【STM32学习5】STM32使用printf函数 打印到电脑串口助手

    本文所使用的方法与代码参考自正点原子 xff0c 如果想要详细了解这方面的知识 xff0c 请阅读正点原子官方提供的文档 一 背景 在开发STM32应用时 xff0c 将一些信息通过串口打印到电脑上是常用的调试手段 C语言标准库中的prin
  • linux下socketCAN实现反转过滤——CAN_INV_FILTER的使用

    关于Linux中socket can怎么使用 xff0c CSDN上已经有很多文章介绍 本文重点记录can filter中的 CAN INV FILTER的使用 xff0c 很多文章对此一笔带过 xff0c 且很多文章翻译不全 xff0c
  • [摘抄-Socket-学习中]UDP通信基础代码-server端

    include lt stdio h gt include lt stdlib h gt include lt string h gt include lt sys socket h gt include lt netinet in h g
  • 四轴飞行器初步——器件选择

    电机 图1 图2 四轴飞行器可以采用无刷电机 xff08 图1 xff09 xff0c 也可以采用有刷电机 xff08 图2 xff09 前者载重能力强 xff0c 一般用于稍大型的多轴飞行器 后者一般用 于小型四轴飞行器 xff0c 如c
  • 如何提取小程序中的文字

    打开微信pc端 xff0c 点击小程序图片 打开qq使用长截图功能 打开adobe acrobat破解版 xff0c 进行文字识别 xff0c 即可得到所有内容
  • 导致数据库中数据不一致的根本原因

    数据库中很有可能存在不一致的数据 一般导致数据库中数据不一致的根本原因有三种情况 第一种是数据冗余造成的 xff0c 第二种是并发控制不当造成的 xff0c 第三种是由于某种原因 xff08 比如软硬件故障或者操作错误 xff09 导致数据

随机推荐