【C++】:bind绑定器和function函数对象机制

news2024/10/9 13:57:41

欢迎来到 破晓的历程的 博客

⛺️不负时光,不负己✈️

文章目录

    • 引言
    • function函数对象
      • function引入
      • 细讲function
      • 体验function在工程实践中的优势
    • 模拟实现function函数对象机制
    • bind绑定器
      • 基本语法
      • 示例
        • 1. 绑定普通函数
        • 2. 使用占位符
        • 3. 绑定成员函数
        • 4. 绑定 lambda 表达式
      • 注意事项
    • 使用bind+function完成线程池设计
      • 何为线程池?
      • 设计代码

引言

在C++中工程实践中,bind绑定器和function函数对象非常常用,而且bind+function简直就是无敌的存在。本篇博客中,我们力求用最小的成本搞懂它们,让你用起来得心应手

function函数对象

以减少理解成本为目地,我们先学习function。

function引入

function的作用是将具有相同调用形式的不同类型可调用对象进行类型统一。

相同的调用形式可以简单理解为:参数列表和返回值相同。

C++常见可调用对象有:函数、指针、匿名函数(lambda表达式)、函数对象(重载了函数调用运算符的类)以及使用bind创建的对象。
例如以下几个可调用对象具有相同的调用形式:

// 函数
int add(int a, int b) {
    return a + b;
}

// lambda表达式
auto sub = [](int a, int b) -> int {
  return a - b;
};

// 函数对象
class prod{
public:
    int operator() (int a, int b) {
        return a * b;
    }
};

这些可调用对象的调用形式相同,甚至拥有相同的功能,但是却由于类型千差万别无法统一处理。例如,我们想要统一调用以上可调用对象,使用‘+’来调用add函数,而使用‘-’调用sub匿名函数…
使用分支语句(if else if else)当然是可以做到的,但更推荐以下方式:

/*  使用map映射字符到可调用对象。
	但需要注意,映射的前提是function对不同类型的可调用对象
进行了类型统一。 */
map<char, function<int(int, int)>> cacul{
    {'+', add},
    {'-', sub},
    {'*', prod()}
};

cout << cacul['*'](5, 4) << endl;

细讲function

funciotn是从c++11开始支持的特性,使用它需要包含头文件functional
在cppreference中解释为:类模板std::function是一个通用的多态函数包装器。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。
通俗的来说可以把它当做一个函数指针来使用

让我们来感受一下:
function的模板是 std::function<返回值类型(传入参数类型)> 方法名
这里传入参数类型可以是自己定义的

举几个简单的例子:

function<int(int, int)> func

这表示:定义了一个返回值为int,参数有两个,从左往右为int,int类型的函数指针

int sum(int a, int b)
{
	return a + b;
}
int main()
{
	using func_t = function<int(int, int)>;
	func_t func = sum;
	cout<<func(10,10);
}

这里,我创建了一个返回值为int,参数有两个,从左往右为int,int类型的函数指针。然后用该函数指针创建一个对象func,将sum「函数名表示该函数的地址」赋值给func。然后就可以通过func调用sum函数。
或者也可以这样:

int main()
{
	function<int(int, int)> func = sum;
	cout<<func(10,10);
}

用也许这样更容易被理解。

体验function在工程实践中的优势

假如我们要设计一个图书管理系统,该系统提供的服务有:借书、查询书、还书。假设这些函数的函数签名都是一样的「即返回值类型和参数类型都是相同的」。我们习惯将服务编号,比如用户输入1表示要获取借书服务,用户输入2表示查询书服务,用户输入3表示还书服务。
此时的开发者有两种设计方案「大体框架,不包括不同模块设计的具体细节」

  • 使用switch判断语句,但是这种框架的劣性在于如果增加一个模块需要进行大范围修改。
  • 使用function和map数据结构相结合。

接下来我们重点介绍第二种框架
本着说明问题,其他一切从简的原则 我们可以这样设计


#include<iostream>
#include<functional>
#include<map>
using namespace std;
void borrow()
{
	std::cout << "borrow books" << std::endl;
}
void lend()
{
	std::cout << "Return the book" << std::endl;

}
void search()
{
	std::cout << "Search book" << std::endl;
}
int main()
{
	using func_t = std::function<void()>;
	std::map<int, func_t> Map;
	Map.insert({ 1,borrow });
	Map.insert({ 2,lend });
	Map.insert({ 3,search });
	while (1)
	{
		int option = 0;
		std::cout << "请选择:";
		std::cin >> option;
		auto it = Map.find(option);
		if (it == Map.end())
		{
			cout << "没有这项服务,请重新选择:" << endl;
		}
		else
		{
			it->second();
		}
	}
}

模拟实现function函数对象机制

如上,我们系统的介绍了function的使用方法和应用,但是它究竟是如何实现的呢?我们一切来探究一下

#include<iostream>
#include<string>
template<class R,class A>
class myfunction<R(A)>
{
public:
	using PFUNC = R(*)A;
	myfunction(PFUNC pfunc)
		:_pfunc(pfunc)
	{}
	R operator()(A ter)
	{
		return _pfunc(str);
	}
priavte:
	PFUNC _pfunc;

};
void hello(std::string str)
{
	std::cout << str << std::endl;
}
int main()
{
	myfunction<void(std::string)> func_t = hello;
	func_t("hello world");
}

但是如果函数签名发生变化,我们就得写不同的部分特例化模板,其实我们可以这样做:

template<class T,class... A1>
class my_function<T(A1...)>
{
public:
	using func_t = T(*)(A1...);
	my_function(func_t func)
		:func_(func)
	{}
	R operator()(A1... args)
	{
		return func_(args...);
	}
private:
	func_t func_;
};

这就是模板的强大之处。

bind绑定器

关于bind绑定器,百度百科是这样说的:
std::bind 是 C++11 引入的一个标准库函数,它位于 functional 头文件中。std::bind 可以用来绑定函数的参数,或者将成员函数和对象绑定在一起,生成一个新的可调用对象(也称为函数对象)。这个新生成的对象可以像普通函数一样被调用,但其内部实际上会调用我们最初绑定的那个函数或成员函数。

基本语法

#include <functional> // 包含 std::bind 的头文件

auto bound_function = std::bind(function, arg1, arg2, ..., argN);
  • function 是要绑定的函数或可调用对象。
  • arg1, arg2, ..., argN 是传递给 function 的参数,可以是具体的值,也可以是占位符 _1, _2, ...(这些占位符定义在 <placeholders> 头文件中,通常通过 std::placeholders::_1 等方式访问)。

示例

1. 绑定普通函数
#include <iostream>
#include <functional>

void print_sum(int a, int b) {
    std::cout << "Sum: " << a + b << std::endl;
}

int main() {
    auto bound_print_sum = std::bind(print_sum, 5, 10);
    bound_print_sum(); // 输出: Sum: 15
    return 0;
}
2. 使用占位符
#include <iostream>
#include <functional>
#include <placeholders> // 包含 std::placeholders 的头文件

void print_sum(int a, int b) {
    std::cout << "Sum: " << a + b << std::endl;
}

int main() {
    using namespace std::placeholders;
    auto bound_print_sum = std::bind(print_sum, _1, 10);
    bound_print_sum(5); // 输出: Sum: 15
    bound_print_sum(20); // 输出: Sum: 30
    return 0;
}
3. 绑定成员函数
#include <iostream>
#include <functional>
#include <placeholders>

class MyClass {
public:
    void print_sum(int a, int b) {
        std::cout << "Sum: " << a + b << std::endl;
    }
};

int main() {
    MyClass obj;
    using namespace std::placeholders;
    auto bound_print_sum = std::bind(&MyClass::print_sum, &obj, _1, 10);
    bound_print_sum(5); // 输出: Sum: 15
    return 0;
}

注意,在绑定成员函数时,第一个参数需要是对象的指针或引用。

4. 绑定 lambda 表达式
#include <iostream>
#include <functional>
#include <placeholders>

int main() {
    auto lambda = [](int a, int b) { std::cout << "Sum: " << a + b << std::endl; };
    auto bound_lambda = std::bind(lambda, _1, 20);
    bound_lambda(10); // 输出: Sum: 30
    return 0;
}

注意事项

  1. 性能std::bind 生成的函数对象在调用时,可能会引入额外的间接调用开销,因此在某些性能敏感的场景中,需要谨慎使用。
  2. 兼容性std::bind 是 C++11 引入的特性,因此确保你的编译器支持 C++11 或更高版本。
  3. 替代方案:在 C++11 及以后的版本中,lambda 表达式通常是一个更灵活和直观的选择,用于实现类似的功能。

通过上面的示例和解释,你应该对 std::bind 的基本用法有了初步的了解。在实际开发中,根据具体需求选择合适的工具和方法来实现功能。

使用bind+function完成线程池设计

何为线程池?

线程池是一种高效的设计方案。我们事先准备若干个线程,然后给不同的线程分配不同的任务。

设计代码

#include<iostream>
#include<functional>
#include<string>
#include<thread>
#include<vector>
using namespace std;
using namespace std::placeholders;
class Thread
{
public:
	Thread(function<void()>Way)
		:_Way(Way)
	{}
	thread start()
	{
		thread t(_Way);
		return t;

	}
private:
	function<void()> _Way;
};
class ThreadPoll
{
public:
	ThreadPoll()
	{}
	~ThreadPoll()
	{
		for (auto it : _poll)
		{
			delete it;
		}
	}
	void start(int size)
	{
		for (int i = 0; i < size; i++)
		{
			_poll.push_back(new Thread(std::bind(&ThreadPoll::ThreadWay, this, i)));

		}
		for (auto it : _poll)
		{
			_hander.push_back(it->start());
		}
		for (auto &it : _hander)
		{
			it.join();
		}
	}
private:
	void ThreadWay(int i)
	{
		cout << "thread %d run........" << i << endl;
	}
	vector<Thread*> _poll;
	vector<thread> _hander;
};
int main()
{
	ThreadPoll it;
	it.start(20);
}
``

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2199186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Android Studio】基础入门(一)—— 创建第一个 Android 项目

文章目录 前言一、创建项目二、启动模拟器三、运行程序 前言 Android Studio是谷歌官方推出的免费集成开发环境&#xff0c;专为安卓应用开发而设计&#xff0c;集成了代码编写、调试、界面设计及性能分析等多种强大功能&#xff0c;支持Java和Kotlin语言&#xff0c;极大提升了…

【vue2.7.16系列】手把手教你搭建后台系统__搭建开发环境(1)

安装开发环境 安装nodejs 使用nvm管理多个node版本。nvm-npm-nrm-配置介绍。本系列文章使用node14.19.3&#xff0c;安装好node后&#xff0c;npm也就安装好了&#xff0c;npm版本是v6.14.7 安装vue/cli 5.0.8 npm install -g vue/cli创建项目 vue create admin # 选项&am…

Compose 一个联系人索引列表实现

一、前言 该功能是一个左侧为字符索引列表&#xff0c;右侧为联系人列表的实现&#xff0c;点击左侧字符索引可以自动滑动到对应联系人条目。该功能代码实现绝大多数有通义灵码完成。实现效果如下&#xff1a; 关键代码 data class Contact(val name: String)class Contact…

unix中如何申请进程调度的优先级

一、前言 unix系统中&#xff0c;进程的调度是由内核决定的。在一个系统中&#xff0c;进程的优先级越高&#xff0c;表示其在一定时间中占用cpu的时间越久。本文将介绍unix系统如何修改以及获取进程的优先级。 二、nice值 nice值是unix系统中用于表征进程优先级的一个参数。…

MES系统中的正向追溯与反向追溯管理

随着制造业的日益发展&#xff0c;生产过程中的质量控制和管理变得尤为关键。MES系统作为一种实现车间生产管理和控制的重要工具&#xff0c;其追溯功能在生产过程中起着至关重要的作用。 一、MES系统概述 MES系统是一套面向制造企业车间执行层的生产信息化管理系统。它通过对…

药品名不得命名保健食品

益安宁丸为同溢堂药业有限公司所独家生产的一款中成药&#xff0c;在内地市场和港澳地区均有上市。

如何在算家云搭建MuseTalk(数字人)

一、 MuseTalk 简介 MuseTalk 是由腾讯团队开发的先进技术&#xff0c;它是一个实时的音频驱动唇部同步模型。该模型能够根据输入的音频信号&#xff0c;自动调整数字人物的面部图像&#xff0c;使其唇形与音频内容高度同步。 二、 MuseTalk 一键使用 基础环境最低要求说明&…

肺结节分割与提取系统(基于传统图像处理方法)

Matlab肺结节分割(肺结节提取)源程序&#xff0c;GUI人机界面版本。使用传统图像分割方法&#xff0c;非深度学习方法。使用LIDC-IDRI数据集。 工作如下&#xff1a; 1、读取图像。读取原始dicom格式的CT图像&#xff0c;并显示&#xff0c;绘制灰度直方图&#xff1b; 2、图像…

深圳市步步精科技有限公司荣获发明专利,彰显技术研发实力

2024年8月13日&#xff0c;深圳市步步精科技有限公司&#xff08;BBJconn&#xff09;正式获得了其新开发的防水连接器专利&#xff0c;授权公告号为CN 118352837 B。这项技术的突破标志着公司在连接器领域的持续创新&#xff0c;进一步巩固了其行业领先地位。 专利技术概述 此…

JavaScript 常用输出语句详解

目录 非 VIP 用户可前往公众号“前端基地”进行免费阅读 alert弹出警告框 Console 控制台输出 Document.write 直接在文档中显示 Prompt 弹出用户输入对话框 非 VIP 用户可前往公众号“前端基地”进行免费阅读 alert弹出警告框 在 JavaScript 中,alert可用于弹出…

Android Preference的使用以及解析

简单使用 values.arrays.xml <?xml version"1.0" encoding"utf-8"?> <resources><string-array name"list_entries"><item>Option 1</item><item>Option 2</item><item>Option 3</item&…

【权威出版|稳定检索】2024年信息学、网络与计算机应用技术国际会议(INCAT 2024)

2024 International Conference on Informatics, Networking, and Computer Application Technology 【1】大会信息 会议名称&#xff1a;2024年信息学、网络与计算机应用技术国际会议 会议简称&#xff1a;INCAT 2024 大会时间&#xff1a;请查看官网 大会地点&#xff1a;中…

携程酒店含价格全国城市分析1

在做一些城市分析、学术研究分析、商业选址、商业布局分析、住宿业分析等数据分析挖掘时&#xff0c;携程酒店的数据参考价值非常大&#xff0c;可以从酒店档次、分布、价格、特色服务等多维度来分析研究。 具体分析研究的字段维度包括酒店名、星级、星级类型、酒店标签、酒店…

通过GIS数据对比北京西城区和鹤岗工农区,数据解释为啥房价差异那么大

这两个区域都是所在城市的核心区域&#xff0c;而且北京西城区是老城区。面积不像其它区域那么大&#xff0c;所以对比起来有一定的代表性&#xff01;我从医疗机构和企业单位两方面进行了对比&#xff0c;他们分别可以代表我们的生活和就业&#xff01;当然我也想对学校教育对…

期权懂|期权中的看涨期权与看跌期权是什么意思?

本期让我懂 你就懂的期权懂带大家来了解&#xff0c;期权中的看涨期权与看跌期权是什么意思&#xff1f;有兴趣的朋友可以看一下。期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 期权中的看涨期权与看跌期权是什么意思&#xf…

sql-lab打不开网页了(没解决)

早上起来想打开sql-lab&#xff0c;发现变成这样了&#xff0c;昨天好像是因为aws&#xff0c;打开了一些服务&#xff0c;那我也没关闭一些服务吧.. 服务正常开启 也有mysql服务 尝试重启服务&#xff0c;发现问题&#xff0c;ok啊&#xff0c;还是很简单的&#xff0c;提示有…

Transformer宝藏入门教程,五天肝疯了—Transformer最全面的入门指南

前言 随着 BERT、GPT 等大规模语言模型的兴起&#xff0c;越来越多的公司和研究者采用 Transformers 库来构建 NLP 应用。本文档教程里包括了自然语言处理、Transformer模型、注意力机制、pytorch、微调预训练模型、翻译任务、序列标注任务、文本摘要等等模块 一、内容介绍 …

JVM篇(学习预热 - 云原生时代的JVM(GraalVM))(持续更新迭代)

目录 一、GraalVM是什么&#xff1f; 二、GraalVM有哪些特点&#xff1f; 1. 高性能 2. 多语言支持 3. 互操作性 4. 安全性 三、GraalVM的应用效果 1. 提高性能 2. 简化开发 3. 降低成本 4. 节省资源 5. 支持云环境 四、使用GraalVM编译springboot应用程序 1. 下载…

【JAVA面试题】Java和C++主要区别有哪些?各有哪些优缺点?

文章目录 强烈推荐前言区别&#xff1a;1. 语法和编程风格2.内存管理3.平台独立性4.性能5.指针和引用6.多线程7.使用场景 Java 的优缺点优点&#xff1a;缺点&#xff1a; C 的优缺点优点&#xff1a;缺点&#xff1a; 总结专栏集锦 强烈推荐 前些天发现了一个巨牛的人工智能学…

ARMv7/v8m GCC Compilation

00-Guide01-Toolchains02-GNU binutils03-ELF file04-Compilation process05-CFLAGS options06- Linker Scripts07 -Link Options08-Map file09-Static and dynamic lib10-Secureboot and image11-Builtin functions12-Function Attributes