【C++11】function包装器和bind包装器的简单使用

news2025/2/4 23:58:41

function

  • function 包装器
    • 一些场景下模板的低效性
    • 包装器 function 修复问题
    • 包装成员函数的注意事项
    • 一道例题
    • function包装器的意义
  • bind 包装器
    • bind 包装器介绍
    • bind 包装器可调整传参顺序
    • bind 包装器可绑定固定参数
    • bind 包装器的意义

C++11提供了多个包装器(wrapper,也叫适配器[adapter])。这些对象用于给其他编程接口提供更一致或更合适的接口。

function 包装器

一些场景下模板的低效性

请看下面的代码行:

result = f(q);

f 是什么?可以是函数名、函数指针、函数对象或有名称的lambda表达式。这些都是可调用的类型(callable type)。鉴于可调用的类型这么丰富,可能导致模板的效率很低。来看一个简单的案例:

template <class T,class F>
T use_f(T v, F f)
{
	static int count = 0;
	count++;
	cout << " use_f count = " << count
		<< ", &count = " << &count << endl;
	return f(v);
}
class Fp
{
public:
	Fp(double a = 1.0) : a_(a){}
	double operator()(double p) { return a_ * p; }
private:
	double a_;
};

class Fq
{
public:
	Fq(double a = 1.0) : a_(a){}
	double operator()(double q) { return a_ + q; }
private:
	double a_;
};

模板use_f使用参数 f 表示调用类型:return f(v);
接下来,下面程序调用模板函数 use_f 6次

double dub(double x) { return 2.0 * x; }
double square(double x) { return x * x; }
int main()
{
	double y = 1.21;
	cout << "Function pointer dub : " << endl;
	cout << " " << use_f(y, dub) << endl;

	cout << "Function pointer square : " << endl;
	cout << " " << use_f(y, square) << endl;

	cout << "Function object Fp : " << endl;
	cout << " " << use_f(y, Fp(5.0)) << endl;

	cout << "Function object Fq : " << endl;
	cout << " " << use_f(y, Fq(5.0)) << endl;

	cout << "Lambda exepression 1:" << endl;
	cout << " " << use_f(y, [](double u) {return u * u; }) << endl;

	cout << "Lambda exepression 2:" << endl;
	cout << " " << use_f(y, [](double u) {return u + u / 2.0; }) << endl;
	return 0;
}

在每次调用中,模板参数T都被设置为double类型。那模板参数F呢?每次调用时,F都接受一个double值并返回一个double值,在6次的use_f()调用中,好像F的类型都相同,因此只会实例化模板一次?这是错误的。来看输出结果。

Function pointer dub :
 use_f count = 1, &count = 00C0C140
 2.42
Function pointer square :
 use_f count = 2, &count = 00C0C140
 1.4641
Function object Fp :
 use_f count = 1, &count = 00C0C150
 6.05
Function object Fq :
 use_f count = 1, &count = 00C0C154
 6.21
Lambda exepression 1:
 use_f count = 1, &count = 00C0C474
 1.4641
Lambda exepression 2:
 use_f count = 1, &count = 00C0C478
 1.815

模板函数 use_f()有一个静态成员 count,可根据它的地址确定模板实例化了多少次。有5个不同的地址,这表面模板 use_f() 有5个不同的变化。

接下来解释其中的原因:
首先,先看下面的调用:

use_f(y,dub);

其中的dub是一个函数名称,该函数接受一个double参数并返回一个double值。函数名是指针,因此参数F的类型为double(*)(double):一个指向这样的函数指针,即它接受一个double参数并返回一个double值。

下一个调用如下:

use_f(y,square);

第二个参数的类型也是double(*)(double),因此该调用使用的 use_f() 实例化与第一个调用相同。
在接下来的两个 use_f() 调用中,第二个参数为对象,F的类型分别为Fp 和Fq ,因此将为这些F值实例化两次。
最后两个调用将F 的类型设置为Lambda表达式使用的类型,本质上是仿函数,所以也会两个对象(匿名对象)。

包装器 function 修复问题

包装器的原型如下:

// 类模板原型如下

template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

模板参数说明:

Ret: 被调用函数的返回类型

Args…:被调用函数的形参

使用包装器 function 重写上述程序,使其只使用 use_f() 的一个实例而不是 5 个。上面程序中 函数指针、函数对象和lambda表达式有一个相同的地方。它们都接受一个double 参数并返回一个double值。可以说它们的调用特征标(call signatute) 相同。调用特征标是由返回类型以及括号括起并用逗号分隔的参数类型列表定义的,因此,这六个实例的特征标都是double(double)。
模板function是在头文件functional中声明的,它从调用特征标的角度定义了一个对象,可用于包装调用特征标相同的函数指针、函数对象或lambda表达式。例如,下面的声明创建一个名为 fdci 的function 对象,它接受一个char 参数和一个int 参数,并返回一个double 值:

std::function<double(char,int)> fdci;

然后,可以接受一个char 参数和一个 int 参数,并返回一个double值的任何函数指针、函数对象或lambda表达式赋给它。

在上面程序中,所以可调用参数的调用特征标都相同:double(double)。要修复程序减少实例化次数,可使用 function<double(double)> 创建六个包装器,用于表示6个函数、函数符和lambda。这样,在对use_f 的全部6次调用中,让F的类型都相同(function<double(double)>),因此只实例化一次。
代码如下:

int main()
{
	double y = 1.21;
	function<double(double)> f1 = dub;
	function<double(double)> f2 = square;
	function<double(double)> f3 = Fq(5.0);
	function<double(double)> f4 = Fp(5.0);
	function<double(double)> f5 = [](double u) {return u * u; };
	function<double(double)> f6 = [](double u) {return u + u / 2.0; };

	cout << "Function pointer dub : " << endl;
	cout << " " << use_f(y, f1) << endl;

	cout << "Function pointer square : " << endl;
	cout << " " << use_f(y, f2) << endl;

	cout << "Function object Fp : " << endl;
	cout << " " << use_f(y, f3) << endl;

	cout << "Function object Fq : " << endl;
	cout << " " << use_f(y, f4) << endl;

	cout << "Lambda exepression 1:" << endl;
	cout << " " << use_f(y, f5) << endl;

	cout << "Lambda exepression 2:" << endl;
	cout << " " << use_f(y,f6) << endl;
	return 0;
}

执行结果如下:

Function pointer dub :
 use_f count = 1, &count = 007D05E8
 2.42
Function pointer square :
 use_f count = 2, &count = 007D05E8
 1.4641
Function object Fp :
 use_f count = 3, &count = 007D05E8
 6.21
Function object Fq :
 use_f count = 4, &count = 007D05E8
 6.05
Lambda exepression 1:
 use_f count = 5, &count = 007D05E8
 1.4641
Lambda exepression 2:
 use_f count = 6, &count = 007D05E8
 1.815

从上面的输出可知,count的地址都相同,而count的值表明,use_f() 被调用了6次。这表明只有一个实例化,并调用了该实例6次,这缩小的可执行代码的规模。

包装成员函数的注意事项

成员函数分为静态成员函数和非静态成员函数。

class Plus
{
public:
    Plus(int rate = 2) : _rate(rate)
    {}
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return (a + b) * _rate;
    }
private:
    int _rate;
};

静态成员函数的包装:
需要加个作用域

function<int(int, int)> f1 = Plus::plusi;

非静态成员的包装:
C++11规定,非静态成员的包装除了加域名外,还要加上&符号。
非静态成员函数的参数是默认带this指针的,所以包装器参数列表里要加上类名。

function<int(Plus,int, int)> f2 = &Plus::plusd;
int main()
{
    function<int(int,int)> f1 = Plus::plusi;
    function<int(Plus,int, int)> f2 = &Plus::plusd;
    cout << f1(1, 1) << endl;//2
    cout << f2(Plus(3),1, 1) << endl;//6,匿名对象方式使用
    Plus p(3);
    cout << f2(p, 1, 1) << endl;//6,创建了一个对象再去调用
    return 0;
}

一道例题

这是力扣上一道逆波兰表达式的题,可以尝试用map结合包装器写一下。
150.逆波兰表达式求值

在这里插入图片描述

求解逆波兰表达式的步骤如下:

定义一个栈,依次遍历所给字符串。 如果遍历到的字符串是数字则直接入栈。
如果遍历到的字符串是加减乘除运算符,则从栈定抛出两个数字进行对应的运算,并将运算后得到的结果压入栈中。
所给字符串遍历完毕后,栈顶的数字就是逆波兰表达式的计算结果。

这是不用map和包装器的写法:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;

        for(auto& i : tokens)
        {
            if(i == "+" || i == "-" || i == "*" || i == "/")
            {
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();
                switch(i[0])
                {
                    case '+' : 
                        s.push(right + left);
                        break;
                    case '-' :
                        s.push(left - right);
                        break;
                    case '*' :
                        s.push(right * left);
                        break;
                    case '/' :
                        s.push(left / right);
                        break;
                }
            }
            else
            {
                s.push(stoi(i));//字符串转整数
            }
        }
        return s.top();
    }
};

这是结合了map和包装器的写法:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;
        map<string,function<int(int,int)>> op = 
        {
            {"+",[](int a,int b){return a + b;}},
            {"-",[](int a,int b){return a - b;}},
            {"*",[](int a,int b){return a * b;}},
            {"/",[](int a,int b){return a / b;}}
        };
        for(auto& i : tokens)
        {
            if(op.count(i))
            {
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();
                s.push(op[i](left,right));
            }
            else
            {
               s.push(stoi(i));//字符转整数
            }
        }
        return s.top();
    }
};

function包装器的意义

将可调用对象的类型进行统一,便于对其进行统一化管理。
包装后明确了可调用对象的返回值和形参类型,更加方便使用者使用

bind 包装器

bind 包装器介绍

bind也是一种函数包装器,也叫做适配器。它可以接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,C++中的bind本质是一个函数模板。

bind函数模板的原型如下:

template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);

模板参数说明:

fn:可调用对象。
args…:要绑定的参数列表:值或占位符。

调用bind的一般形式为:auto newCallable = bind(callable, arg_list);

解释说明:

callable:需要包装的可调用对象。
newCallable:生成的新的可调用对象。
arg_list:逗号分隔的参数列表,对应给定的callable的参数。当调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置,比如_1为newCallable的第一个参数,_2为第二个参数,以此类推。
在使用的时候占位符的时候需要指明作用域。
在这里插入图片描述
在这里插入图片描述
此外,除了用auto接收包装后的可调用对象,也可以用function类型指明返回值和形参类型后接收包装后的可调用对象。

bind 包装器可调整传参顺序

#include <iostream>
#include <functional>

using namespace std;

void Print(int a, int b)
{
	cout << "a = " << a;
	cout << " b = " << b << endl;
}

int main()
{
	Print(10, 20);
	function<void(int,int)> f1 = bind(Print, placeholders::_1, placeholders::_2);//未调整传参顺序
	f1(10, 20);
	
	//调整传参顺序
	auto f2 = bind(Print, placeholders::_2, placeholders::_1);
	f2(10, 20);
	return 0;
}

上述代码中Print()分别打印传入的参数的值。
Print(10,20); 是普通的函数调用。

function<void(int,int)> f1 = bind(Print, placeholders::_1, placeholders::_2);//未调整传参顺序
f1(10, 20);

这段代码是先用bind包装器包装,但是未调整传参顺序,然后再用function包装,然后调用f1

//调整传参顺序
auto f2 = bind(Print, placeholders::_2, placeholders::_1);
f2(10, 20);

这段代码是用了bind包装器包装,且调整了传参顺序,然后用auto自动类型推导,然后调用f2

执行结果如下:

a = 10 b = 20
a = 10 b = 20
a = 20 b = 10

可以看到,bind包装器包装后,然后改变了传参顺序,起到了效果。

bind 包装器可绑定固定参数

在上面funtion包装器包装成员函数的时候有这么一段代码:

class Plus
{
public:
    Plus(int rate = 2) : _rate(rate)
    {}
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return (a + b) * _rate;
    }
private:
    int _rate;
};

function包装非静态成员函数时因为非静态成员函数默认有个this,在包装和调用时还要额外加上一个对象

int main()
{
    function<int(Plus, int, int)> f2 = &Plus::plusd;
    cout << f2(Plus(3), 1, 1);//6
	return 0;
}

如果结合bind包装器的话,在包装时可以把这个对象绑定住,那么在调用时只需传2个参数即可。

int main()
{
    //function<int(Plus, int, int)> f2 = &Plus::plusd;
    //cout << f2(Plus(3), 1, 1);//6
    auto f2 = bind(&Plus::plusd, Plus(3), placeholders::_1,placeholders::_2);
    cout << f2(1, 1);//6
	return 0;
}

bind 包装器的意义

可以对函数参数的顺序进行灵活调整。
将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。

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

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

相关文章

BYOVD!干掉EDR/XDR/AVs进程工具

工具介绍 利用gmer驱动程序有效地禁止使用或杀死EDR和AV&#xff0c;它可以流畅地绕过HVCI&#xff1b;该样本来自 loldrivers&#xff1a;https://www.loldrivers.io/drivers/7ce8fb06-46eb-4f4f-90d5-5518a6561f15/ 关注【Hack分享吧】公众号&#xff0c;回复关键字【230614…

docker安装mariadb,并在宿主机连接docker中启动的mariadb

这篇文章主要介绍怎么在docker中安装一个mariadb数据库&#xff0c;然后在我们的电脑本机上连接虚拟机上docker运行的mariadb数据库。 首先&#xff0c;需要安装一个虚拟机软件&#xff0c;通过虚拟机软件安装一个linux操作系统&#xff0c;本篇文章安装的是ubuntu&#xff0c…

一、基础-3、MySQL卸载

1.、停止MySQL服务 winR 打开运行&#xff0c;输入 services.msc 点击 "确定" 调出系统服务。 2. 卸载MySQL相关组件 打开控制面板 ---> 卸载程序 ---> 卸载MySQL相关所有组件。 3. 删除MySQL安装目录 4. 删除MySQL数据目录 数据存放目录是在 C:\ProgramDat…

No.185# 技术管理框架知识点随记

引言 陆续参加了公司组织的两场关于技术管理的培训&#xff0c;时间一长也快忘的七七八八了。本文以刘建国《执行技术人管理之路》为基础框架&#xff0c;将知识点做了整理&#xff0c;在需要的时候翻翻。本文主要内容有&#xff1a; 技术管理之角色认知技术管理之管理规划技术…

【技能实训】DMS数据挖掘项目-Day11

文章目录 任务12【任务12.1】创建用户信息表【任务12.2】在com.qst.dms.entity下创建用户实体类User&#xff0c;以便封装用户数据【任务12.3】在com.qst.dms.service下创建用户业务类UserService【任务12.4】在项目根目录下创建图片文件夹images&#xff0c;存储dms.png【任务…

了解数据科学中的异常检测

大家好&#xff0c;本文将简要介绍一下异常检测&#xff0c;并指导通过不同的技术来识别异常。 如果你正在处理数据&#xff0c;那么无论是现在还是将来&#xff0c;都可能会遇到一项非常重要的任务 —— 异常检测。它在许多领域中都有很大的应用&#xff0c;如制造业、金融和…

visual studio 2017直接打开文件夹时,选择当前项目或者整个解决方案时,按快捷键查找时显示未找到以下指定文本

有的时候只想要打开一整个文件夹来看里面的代码&#xff0c;平时一般用Qt&#xff0c;但是感觉在打开整个文件夹看代码方面&#xff0c;Qt没有VS方便&#xff0c;于是选择了VS&#xff0c;安装的是VS2017&#xff0c;然后发现有个问题&#xff0c;CtrlF查找时&#xff0c;如果选…

报错:Invalid bound statement (not found): com.web.sysmgr.mapper.UserMapper.login

报错&#xff1a;Invalid bound statement (not found): com.web.sysmgr.mapper.UserMapper.login 原因&#xff1a; 确认是否在扫描Mapper接口时指定了正确的包路径。检查 MapperScan 注解中的包路径是否正确&#xff0c;确保只扫描到需要的Mapper接口。 如果在配置类中去配置…

JQuery 实现点击按钮添加 input 框

前言 用于记录开发中常用到的&#xff0c;快捷开发 需求 比如说&#xff0c;我台设备可以设置一个或多个秘钥&#xff0c;有时候我配置一个秘钥时&#xff0c;就不需要多个输入框&#xff0c;当我想配置多个秘钥时&#xff0c;就需要添加多个输入框。 实现 HTML <div…

Hadoop 之 HDFS 伪集群模式配置与使用(二)

HDFS 配置与使用 一.HDFS配置二.HDFS Shell1.默认配置说明2.shell 命令 三.Java 读写 HDFS1.Java 工程配置2.测试 一.HDFS配置 ## 基于上一篇文章进入 HADOOP_HOME 目录 cd $HADOOP_HOME/etc/hadoop ## 修改文件权限 chown -R root:root /usr/local/hadoop/hadoop-3.3.6/* ## …

JVM 运行流程、类加载、垃圾回收

一、JVM 简介 1、JVM JVM 是 Java Virtual Machine 的简称&#xff0c;意为 Java 虚拟机。 虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。 常见的虚拟机&#xff1a;JVM、VMwave、Virtual Box。 JVM 和其他两个虚拟机的区别…

Android Profiler 内存分析器使用

Android Profiler是Android Studio的一部分&#xff0c;提供了一个集成的性能分析工具套件&#xff0c;包括内存分析。Android Profiler 工具可提供实时数据&#xff0c;帮助您了解应用的 CPU、内存、网络和电池资源使用情况。 在Android Profiler中&#xff0c;您可以查看内存…

赋能安防“新视界”!智汇云舟亮相中国安防工程商集成商大会

7月14日&#xff0c;备受业界关注的中国安防工程商&#xff08;系统集成商&#xff09;大会暨第67届中国安防新产品、新技术成果展示在上海盛大开幕。来自上海、苏州、南京、无锡等城市的200余位行业领导、嘉宾莅临参会&#xff0c;智汇云舟副总裁陈虹旭受邀出席活动并发表《视…

2、Redis高级特性和应用(发布 订阅、Stream)

Redis高级特性和应用(发布 订阅、Stream) 发布和订阅 Redis提供了基于“发布/订阅”模式的消息机制&#xff0c;此种模式下&#xff0c;消息发布者和订阅者不进行直接通信,发布者客户端向指定的频道( channel)发布消息&#xff0c;订阅该频道的每个客户端都可以收到该消息。 …

【云原生|Docker系列第3篇】Docker镜像的入门实践

欢迎来到Docker入门系列的第三篇博客&#xff01;在前两篇博客中&#xff0c;我们已经了解了什么是Docker以及如何安装和配置它。本篇博客将重点介绍Docker镜像的概念&#xff0c;以及它们之间的关系。我们还将学习如何拉取、创建、管理和分享Docker镜像&#xff0c;这是使用Do…

链表OJ(LeetCode)

文章目录 1.移除链表元素2.反转链表3.链表的中间结点4.倒数第k个结点5.合并两个有序链表6.链表分割7.链表的回文结构8.相交链表9.环形链表10.环形链表Ⅱ1.常规思路2.新型思路【无码】 1.移除链表元素 法一&#xff1a;遍历删除 struct ListNode {int val;struct ListNode* nex…

采集极验4滑块验证码图片数据

在网络安全领域&#xff0c;验证码是一种常见的用于验证用户身份或防止恶意机器人攻击的技术。而极验4滑块验证码作为一种广泛应用的验证码形式&#xff0c;其具有较高的安全性和防御能力。本文将以获取极验4滑块验证码图片数据为主题&#xff0c;介绍相关技术和方法。 一、极…

【Jenkins入门到实战】忽如一夜春风来,千树万树梨花开

自动化运维之Jenkins 前提条件&#xff1a;安装好jdk &#xff08;版本要求11-17&#xff09;并配置好环境变量 一、Jenkins 1、Jenkins是什么 Jenkins是一个开源的持续集成服务&#xff0c;用于实施软件开发和发布流程。它帮助软件开发和运维团队在构建、测试和部署软件上实…

cesium的使用

cesium的使用 cesium的使用创建一个vue项目 vuevitecesium参数的使用常用点位标记删除动态渲染路线借助truf.js的算法进行渲染地块的实现topojson cesium的使用 1.下载或者安装cesium的插件 官方文档 下载下来后创建文件夹整个包引入 2.生成token 新的包应该有默认token如果没…

前端学习记录~2023.7.10~CSS杂记 Day5

前言一、样式化表格1、一个典型的 HTML 表格2、进行样式化&#xff08;1&#xff09;间距和布局&#xff08;2&#xff09;简单地排版&#xff08;3&#xff09;图形和颜色&#xff08;4&#xff09;图案&#xff08;5&#xff09;样式化标题 最终效果如下 ![在这里插入图片描述…