C++11:包装器

news2024/11/15 15:45:13

文章目录

  • 1. 介绍
  • 2. function包装器
    • 2.1 介绍
    • 2.2 示例1
      • 用法
    • 2.3 示例2
    • 2.4 function包装器的功能
      • 统一类型
      • 简化代码
    • 2.5 意义
  • 3. bind包装器
    • 3.1 介绍
    • 3.2 bind包装器的功能
      • 绑定固定参数
    • 3.3 意义

1. 介绍

C++ 包装器是一种用于给其他编程接口提供更一致或更合适的接口的技术。它可以包装任何类型的可调用实体,如普通函数,函数对象,lambda表达式、类的成员函数等。C++ 包装器有多种类型,如bind, mem_fn, reference_wrapper, function等。

包装器的本质是一种类模板,它可以提高模板的效率,简化代码,增强可读性和可维护性。

下面介绍C++中的两种包装器:function包装器和bind包装器。

2. function包装器

2.1 介绍

function类模板的原型:

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

其中:

  • Ret:被包装的可调用对象的返回值类型。
  • Args...:被包装的可调用对象的形参类型。

2.2 示例1

下面用function包装器对函数、函数对象、lambda表达式进行包装。

#include <iostream>
using namespace std;

// 简单函数
int add(int a, int b)
{
    return a + b;
}
// 函数对象
struct subtract
        {
    int operator()(int a, int b)
    {
        return a - b;
    }
};

int main()
{
    function<int(int, int)> op; // 创建函数对象

    op = add; // 简单函数
    cout << "op(10, 5) = " << op(10, 5) << endl; // prints 15

    op = subtract(); // 函数对象
    cout << "op(10, 5) = " << op(10, 5) << endl; // prints 5

    op = [](int a, int b) { return a * b; }; // lambda表达式
    cout << "op(10, 5) = " << op(10, 5) << endl; // prints 50

    return 0;
}

输出:

op(10, 5) = 15
op(10, 5) = 5
op(10, 5) = 50

用法

function<int(int, int)> op

  • <int(...)>,表示返回值类型是int型;
  • <...(int, int)>,表示形参类型都是int型;
  • function<int(int, int)> op,表示创建一个包装器对象

此时包装器还未初始化,如果将函数、函数对象、lambda表达式等赋值给包装器对象op,那么就相当于把函数、函数对象、lambda表达式等装进了一个名为op的盒子,通过这个盒子就能以一个统一的方式使用函数、函数对象、lambda表达式等。

这个统一的方式就是像普通函数一样调用、传参、取返回值。当然,在创建包装器对象的同时也可以初始化。

2.3 示例2

除此之外,包装器还能接受类的成员函数(指针):

class Cpt
{
public:
    static int add(int a, int b)
    {
        return a + b;
    }
    double subtract(double a, double b)
    {
        return a - b;
    }
};
int main()
{
    function<int(int, int)> op1 = &Cpt::add; // 静态成员
    cout << op1(1, 2) << endl;

    function<double(Cpt, double, double)> op2 = &Cpt::subtract; // 非静态成员
    cout << op2(Cpt(), 1, 2) << endl;

    return 0;
}

输出:

3
-1

成员函数指针是一种指向类的非静态成员函数的指针。它的类型声明需要加上类名

注意静态成员函数和非静态成员函数的使用方式:

  • 静态成员函数:取出静态成员函数的地址时,需要通过类名,但&不是必须的;
  • 非静态成员函数:取出非静态成员函数的地址时,需要通过类名,但&是必须的。非静态成员函数的第一个参数是this指针(它是隐藏的),因此在包装时需要指明第一个形参的类型为类的类型。

2.4 function包装器的功能

统一类型

下面定义一个函数模板:

template<class F, class T>
T useF(F f, T x)
{
    static int count = 0;
    cout << "count: " << ++count << endl;
    cout << "&count: " << &count << endl;

    return f(x);
}

其中:

  • F:表示任意的可调用对象,比如函数指针、仿函数、lambda表达式等;

  • T:表示(包装器)传入的参数。

在函数模板内部定义了静态变量count,通过打印它的地址来验证每次调用的是否是同一个函数。

useF函数会有几份取决于编译器如何实例化模板函数。实例化是指编译器根据模板函数和具体的类型参数生成一个特定的函数。一般来说,每种类型参数都会生成一个不同的实例,所以如果你用了三种类型参数,就会有三份useF函数。

// 普通函数
double f(double d)
{
    return d / 2;
}
// 函数对象
struct Functor
{
    double operator()(double d)
    {
        return d / 4;
    }
};
// lambda表达式
auto l = [](double d)
{
    return d / 8;
};
int main()
{
    cout << useF(f, 12.12) << endl; // 函数指针
    cout << useF(Functor(), 12.12) << endl; // 函数对象
    cout << useF(l, 12.12) << endl; // lambda表达式

    return 0;
}

输出:

count: 1
&count: 0x1028b4000
6.06
count: 1
&count: 0x1028b4004
3.03
count: 1
&count: 0x1028b400c
1.515

结果表明,编译器通过不同的模板参数实例化出了3份不同的函数。因为代码中使用了函数模板useF,并且用三种不同类型的参数(函数指针、函数对象和lambda表达式)调用了它。这意味着编译器会为每种参数类型生成一个useF的特化版本。

模板实例化是从一个模板声明和一个或多个模板参数创建一个函数、类或类成员的新定义的过程。创建的定义称为特化。

然而,我们使用包装器的目的是能以一种统一的方式使用函数指针、函数对象和lambda表达式等功能。三次调用useF函数时传入的可调用对象虽然是不同类型的,但这三个可调用对象的返回值和形参类型都是相同的,显然这会对效率造成一定影响。

如果想让编译器只实现一份useF,从而调用函数指针、函数对象和lambda表达式,你可以使用显式实例化。显式实例化是指在代码中明确地指定要生成哪些类型的模板函数,而不是让编译器自动推断。这样可以避免重复或冗余的实例化,并提高编译效率。

例如,在useF函数定义后加上这样一行代码:

template double useF<double(*)(double), double>(double(*)(double), double);

这就告诉编译器只生成一个接受double类型的函数指针和double类型的值作为参数的useF函数,这样就会强制编译器只生成一个useF的特化版本,并且只接受相同类型的参数。然后你就可以用这个函数来调用函数指针、函数对象和lambda表达式了,因为它们都符合这个类型签名。

不过,可以使用包装器,将它们都放进一个盒子里,然后通过这个唯一的盒子实例化出唯一的函数。

int main()
{
    function<double(double)> op; // 实例化包装器

    op = f; // 函数指针
    cout << useF(op, 8.88) << endl;

    op = Functor(); // 函数对象
    cout << useF(op, 8.88) << endl;

    op = l; // lambda表达式
    cout << useF(op, 8.88) << endl;
    return 0;
}

输出:

count: 1
&count: 0x10011c004
4.44
count: 2
&count: 0x10011c004
2.22
count: 3
&count: 0x10011c004
1.11

由于useF的第一个参数的类型都是function类型的,所以只实例化出一个函数。

简化代码

function包装器可以简化代码,因为它们可以让用户使用不同类型的可调用目标作为参数或返回值,而不需要显式地转换或重载。它们还可以让用户绑定一些参数或修改调用顺序,从而创建自定义的函数对象。

例如,逆波兰表达式求值

image-20230302001937593

使用栈和Switch语句可以解决,但是这样会有局限性:增加新的运算也要增加新的选择分支,且分支都取决于根据运算方式。考虑使用包装器简化代码:

  • 建立运算符与其动作(通过函数实现)之间的映射关系,当需要执行某一运算时就可以直接通过运算符匹配相应函数。
  • 当运算类型增加时,就只需要建立新增运算符与其对应函数之间的映射关系即可。
class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int> st;
		map<string, function<long long(long long, long long)>> opMap = {
			{ "+", [](long long a, long long b){return a + b; } },
			{ "-", [](long long a, long long b){return a - b; } },
			{ "*", [](long long a, long long b){return a * b; } },
			{ "/", [](long long a, long long b){return a / b; } }
		};
		for (const auto& str : tokens)
		{
			long long left = 0, right = 0;
			if (str == "+" || str == "-" || str == "*" || str == "/")
			{
				right = st.top();
				st.pop();
				left = st.top();
				st.pop();
				st.push(opMap[str](left, right));
			}
			else
			{
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

2.5 意义

function包装器是一种特殊的类模板,它可以包装一个可调用的目标,并提供一个统一的接口(明确了可调用对象的返回值和形参类型)。它的意义在于可以让你使用不同类型的函数或函数对象,而不需要关心它们的具体实现或参数类型。它也可以让你更容易地复用和组合现有的函数或函数对象。

3. bind包装器

bind包装器是一种函数对象,它可以将一个可调用对象和一些参数绑定在一起,形成一个新的可调用对象,本质是一个函数模板。可以使用std::bind或std::bind_front和std::bind_back来创建bind包装器。

3.1 介绍

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...:要绑定的参数列表:值或占位符。

形式:auto newCallable = bind(callable, arg_list);

其中:

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

占位符:参数列表中可能有形如_1_2_n这样的参数,这些参数被称为占位符。

占位符是一种特殊的对象,它们可以用来表示bind包装器中的可变参数。它们的类型是不确定的,但是它们都可以被默认构造和复制。

当你创建一个bind包装器时,你可以使用占位符来指定哪些参数会在调用时被传递给被绑定的函数对象。占位符的顺序决定了参数的顺序。例如,auto add5 = bind(add, 5, std::placeholders::_1);中,第一个参数是固定为5,而第二个参数是占位符_1,表示它会被调用时传入的第一个参数替换。所以add5(10)相当于add(5, 10)。

你可以使用不同编号的占位符来改变参数的顺序或者重复使用某些参数。例如,auto mul = bind(std::multiplies<int>(), std::placeholders::_1, std::placeholders::_2);中,两个占位符_1和_2分别对应调用时传入的第一个和第二个参数。所以mul(3, 4)相当于std::multiplies()(3, 4)。

但是如果你写成auto mul = bind(std::multiplies<int>(), std::placeholders::_2, std::placeholders::_1);那么两个占位符就交换了位置,所以mul(3, 4)相当于std::multiplies()(4, 3)。

还有一种情况是你可以使用同一个占位符多次来重复使用某个参数。例如,auto square = bind(std::multiplies<int>(), std::placeholders::_1, std::placeholders::_1);中,两个占位符都是_1,表示它们都会被调用时传入的第一个参数替换。所以square(5)相当于std::multiplies()(5, 5)。

即占位符表示newCallable的参数占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置,例如_1为newCallable的第一个参数,_2为第二个参数,以此类推。

包装后的可调用对象的类型可以用auto接收,也可以用function类型指明返回值和形参类型后接收。

3.2 bind包装器的功能

绑定固定参数

下面以函数和参数绑定为例:

#include <iostream>
using namespace std;

int add(int a, int b)
{
    return a + b;
}
int subtract(int a, int b)
{
    return a - b;
}
int main()
{
    function<int(int, int)> f1 = bind(add, placeholders::_1, placeholders::_2);
    cout << f1(1, 2) << endl;

    function<int(int)> f2 = bind(add, placeholders::_1, 2);
    cout << f2(1) << endl;

    function<int(int)> f3= bind(add, 1, placeholders::_1);
    cout << f3(2) << endl;

    function<int(int, int)> f4= bind(subtract, placeholders::_2, placeholders::_1);
    cout << f4(2, 1) << endl;
    return 0;
}

输出:

3
3
3
-1

其中:

  • f1是无意义的绑定,因为这和直接调用函数传参没有区别。
  • f2和f3能提现占位符的作用:占位符_n中的n就是包装器中的参数传入绑定的对象的顺序。
  • f4说明了占位符可以不必按照顺序填写(注意_2_1之前,传入的参数是2,1,结果是(1-2=)-1)。然而这种特性并不常用。

3.3 意义

bind包装器的意义是用来绑定函数调用的某些参数,将可调用对象保存起来,然后在需要的时候再调用。它可以支持普通函数、函数对象和成员函数,并且可以使用占位符来灵活地指定参数。

bind包装器最大的作用就是将某些固定的参数和可调用对象绑定在一起,也可以在某些情况下赋予可调用对象以默认值。

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

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

相关文章

人工智能书籍——《奇点临近》

当人们看到太多相同的时候&#xff0c;也许我们很无知&#xff1b; 当人们看到太多不同的时候&#xff0c;也许我们视野不够大&#xff1b; 当人们同时看到不同和相同的时候&#xff0c;也许这恰是我们的智慧原点。 物质是静止的能量&#xff0c;能量是运动的物质&#xff0c;生…

04 HiveHBase

Hive 一 Hive基本概念 1 Hive简介 学习目标 了解什么是Hive了解为什么使用Hive 1.1 什么是 Hive Hive 由 Facebook 实现并开源&#xff0c;是基于 Hadoop 的一个数据仓库工具&#xff0c;可以将结构化的数据映射为一张数据库表&#xff0c;并提供 HQL(Hive SQL)查询功能&…

05 Spark_Core

01 spark 入门 课程目标&#xff1a; 了解spark概念知道spark的特点&#xff08;与hadoop对比&#xff09;独立实现spark local模式的启动 1.1 spark概述 1、什么是spark 基于内存的计算引擎&#xff0c;它的计算速度非常快。但是仅仅只涉及到数据的计算&#xff0c;并没有涉…

【5】基础语法篇 - VL5 位拆分与运算

VL5 位拆分与运算 1 自己犯的错误 &#xff08;1&#xff09;语法错误 在begin end块 后面加了" ; " case(sel)2b00: begin validout<0; out<0; end;2b01: begin validout<1; out<d0 d1; end;2b10: begin validout<1; out<d0 d2; end;2b11: be…

分享几个常用的运维 shell 脚本

今天咸鱼给大家分享几个不错的 Linux 运维脚本&#xff0c;这些脚本中大量使用了 Linux 的文本三剑客&#xff1a; awkgrepsed 建议大家这三个工具都要了解并最好能够较为熟练的使用 根据 PID 显示进程所有信息 根据用户输入的PID&#xff0c;过滤出该PID所有的信息 #! /b…

MySQL(三)SQL优化

SQL优化插入数据大批量数据插入主键优化order by优化group by优化limit优化count优化update优化插入数据 需要一次性往数据库表中插入多条记录&#xff0c;可以从以下三个方面进行优化 insert into tb_test values(1,tom); insert into tb_test values(2,cat); insert into t…

Salesforce 2023财年逆风增长,现金流达历史最高!

在过去的一年里&#xff0c;Salesforce一直是华尔街最关注的公司之一。3月1日&#xff0c;CRM领域的全球领导者Salesforce公布了截至2023年1月31日的第四季度和整个财年的业绩。 Salesforce主席兼首席执行官Marc Benioff表示&#xff1a; Salesforce全年实现了314亿美元的收入…

【备战面试】每日10道面试题打卡-Day6

本篇总结的是计算机网络知识相关的面试题&#xff0c;后续也会更新其他相关内容 文章目录1、HTTP 与 HTTPS 有哪些区别&#xff1f;2、HTTPS的加密过程是什么&#xff1f;3、GET与POST有什么区别&#xff1f;4、讲讲HTTP各个版本的区别&#xff1f;5、HTTP与FTP的区别&#xff…

【C++、C++11】可变参数模板、lambda表达式、包装器

文章目录&#x1f4d6; 前言1. 可变参数模板1.1 万能模板&#xff1a;1.2 完美转发&#xff1a;1.3 可变参数模板的使用&#xff1a;1.4 emplace_back&#xff1a;2. lambda表达式2.1 lambda表达式的定义&#xff1a;2.2 lambda表达式的用法&#xff1a;2.2 - 1 捕捉列表的用法…

manacher算法详解

例题 求一个字符串的最长回文子串的长度 O(N2)O(N^2)O(N2)的解法很容易想&#xff0c;就是从每个字符位置向左右同时拓展&#xff0c;然后检查当前是不是回文&#xff0c;更新长度&#xff0c;可以简单写一下代码 int solve(string &ss){int ans 0;int n ss.length();s…

从参数数量视角理解深度学习神经网络算法 DNN, CNN, RNN, LSTM 以python为工具

从参数数量视角理解深度学习神经网络算法 DNN, CNN, RNN, LSTM 以python为工具 文章目录1. 神经网络数据预处理1.1 常规预测情景1.2 文本预测场景2.全连接神经网络 DNN3.卷积神经网络CNN4.循环神经网络 RNN5.长短期记忆神经网络 LSTMʚʕ̯•͡˔•̯᷅ʔɞʚʕ̯•͡˔•̯᷅ʔ…

【数据结构】详解空间复杂度

Yan英杰的博客 悟已往之不谏 知来者之可追 目录 空间复杂度 ​案例1:计算BubbleSort的空间复杂度&#xff1f; 案例2:计算斐波那契额数列的前N项的空间复杂度 案例3:计算阶乘递归Fac的空间复杂度&#xff1f; 案例4:F1和F2两函数是否使用的同一块空间 案例5:计算该…

git团队合作 - branch分支的使用、主分支合并、冲突处理方案

情景例子开发部3人&#xff0c;组长man&#xff0c; 组员devA&#xff0c;devB&#xff1b;1&#xff09;组长man负责代码合并、冲突处理、检查代码、合并代码到master主分支&#xff1b;2&#xff09;组员devA负责开发3&#xff09;组员devB负责开发git仓库主次分支安排1&…

windows下qt creator 配置编译环境gcc,g++,gdb,cmake

MSVC&#xff1a;即Microsoft Visual C Compiler&#xff0c;即微软自己的编译器 MinGW&#xff1a;我们都知道GNU在Linux下面鼎鼎大名的gcc/g&#xff0c;MinGW则是指Minimalist GNU for Windows的缩写 这里我们选择MinGW&#xff0c;至于Qt中&#xff0c;这两种模式的区别&…

Python入门自学进阶-Web框架——34、富文本编辑器KindEditor、爬虫初步

KindEditor是一个轻量级的富文本编辑器&#xff0c;应用于浏览器客户端。一、首先是下载&#xff1a;http://kindeditor.net/down.php&#xff0c;如下图下载后是解压缩后&#xff1a;红框选中的都可以删除到&#xff0c;这些主要是针对不同的语言编写的示例&#xff0c;因为我…

一文教会你如何简单使用Fegin进行远程服务调用

文章目录1、fegin的基本介绍2、fegin的基本使用步骤3、项目中的实际运用4、测试前言在分布式微服务中&#xff0c;少不了会进行不同服务之间的相互调用&#xff0c;比如A服务要调用B服务中的接口&#xff0c;如何简单方便的实现呢&#xff1f;fegin可以来帮助。 1、fegin的基本…

如何禁止删除或修改RAR压缩包里的文件?很多人不知道这个功能

你是否有过这样的需求&#xff1f;把文件压缩成RAR格式后&#xff0c;需要对压缩包里的文件进行保护&#xff0c;以防别人或者自己误删文件&#xff0c;或者不小心修改了文件内容。 有些小伙伴可能会给压缩包里的文件都设置上“限制编辑”&#xff0c;这虽然也能防止随意更改内…

Stable Diffusion 个人推荐的各种模型及设置参数、扩展应用等合集(不断更新中)

一、说明 | 表示或者 表示 以上 二、模型 适用风景、房子、车子等漫画类风格 模型的VAE不要用模型附带的&#xff0c;好像就是naifu的官方vae&#xff0c;很老了&#xff0c;用 vae-ft-mse-840000-ema-pruned.ckpt 或者是 kl-f8-anime2.ckpt&#xff1b; 嵌入模型要下载作者…

免费集装箱箱号识别API,人工智能企业CIMCAI集装箱识别检测人工智能平台全球4千企业用户,支持API集成二次开发人工智能企业

免费集装箱箱号识别API&#xff0c;人工智能企业CIMCAI集装箱识别检测人工智能平台全球4千企业用户&#xff0c;支持API集成二次开发。箱信息识别及铅封号识别功能免费&#xff0c;顶尖AI集装箱识别率99.98%&#xff0c;全球No.1集装箱人工智能企业CIMCAI打造。中国上海人工智能…

chrome如何查看和修改除了密码,付款方式,地址意外的自动填充表单内容

这种自动填写的内容似乎无法设置。 软件地址&#xff1a;https://sqlitebrowser.org/dl/ 去这里查看地址 https://chromium.googlesource.com/chromium/src//master/docs/user_data_dir.md 比如我是windows&#xff0c;则地址为&#xff1a;C:\Users\用户名\AppData\Local\Go…