C++ 标准库——函数对象和函数适配器

news/2025/2/24 5:27:32

文章目录

    • 函数对象
    • 函数适配器
      • bind
        • bind的重载问题
        • bind的引用问题
      • mem_fn
      • function

C++标准库提供了函数对象和函数适配器功能

函数对象

许多标准库算法都接受函数对象(或函数)参数,用来控制其工作方式。常见的函数对象包括——比较标准、谓词(返回bool的函数)和算术运算。标准库在<functional>中提供了一些常用的函数对象,这部分直接参考:Standard library header。

函数适配器

所谓函数适配器就是就受一个函数参数,返回一个可以用来调用该函数的函数对象。
适配器bind和mem_fn进行实参绑定,也成为柯里化(Currying)或部分求值(partial evaluation)。

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

bind

g=bind(f, arg):g(arg2)等价与f(arg3),其中arg3是通过arg2中的实参替换arg中对应的占位符得到的。

给定一方额函数和一组实参,bind()生成一个可用该函数剩余实参(如果存在)调用的函数对象。
例如:

int func0()
{
    std::cout << "func0" << std::endl;
    return 0;
}

void func(int a, int b, int c)
{
    std::cout << a << "," << b << "," << c << std::endl;
}


        std::function<int(void)> f0 = std::bind(func0);// 也可以使用auto来避免冗余的类型声明
        std::cout << f0() << std::endl;
		
		// 利用占位符告知bind实参在结果函数对象中应该放到什么位置
        std::function<void(int, int, int)> f =
            std::bind(func, std::placeholders::_1, std::placeholders::_2,
                      std::placeholders::_3);
        f(1, 2, 3);
        std::function<void(int, int)> f2 =
            std::bind(func, 6, std::placeholders::_1, std::placeholders::_2);
        f2(1, 2);

运行结果:

func0
0
1,2,3
6,1,2
bind的重载问题

同时,如果要绑定存在重载的函数的参数,必须显式说明绑定的是哪个函数:

int func2(int, int);
double func3(double, double);

auto f = std::bind((double(double,double))func2, std::placeholders::_1, 2);
bind的引用问题

另外,bind接受普通表达式做参数,着导致对引用参数而言,在bind看到他们的时候已经被解引用了。

void inc_func(int &i)
{
    i++;
}
void add_func(int &a, int b)
{
    a += b;
}

        auto f_incr = std::bind(inc_func, std::placeholders::_1);
        int i = 0;
        inc_func(i);
        std::cout << i << std::endl;
        f_incr(i);
        std::cout << i << std::endl;
        auto f_add = std::bind(add_func, i, std::placeholders::_1);
        f_add(3);
        std::cout << i << std::endl;

运行结果:

1
2
2

从结果可以看出,bind的参数已经被解引用了。
为了解决这个问题,标准库提供了一对适配器:refcref,返回一个reference_wrapper。
上面的例子增加下面修订后:

auto f_add_ref =
            std::bind(add_func, std::ref(i), std::placeholders::_1);
        f_add_ref(4);
        std::cout << i << std::endl;

此时正确输出

1
2
2
6

同时,ref也被用于向thread传递引用参数,因为thread的构造函数是可变参数模板。

mem_fn

g=mem_fn(f):若p是一个指针,则g(p,args)表示p->f(args),否则g(p,args)表示p.f(args);args是一个(可能为空的)实参列表。

函数适配器mem_fn生成一个函数对象,可以作为非成员函数调用。
例如:

class A
{
public:
    void print(int a)
    {
        std::cout << a << std::endl;
    }
};

        auto g = std::mem_fn(&A::print);
        A *a = new A;
        // 相当于调用了a->print(4)
        g(a, 4);

mem_fn的主要用途是服务于需要非成员函数的算法,例如:

void draw_all(vector<Shape *>& v)
{
	for_each(v.begin(), v.end(), mem_fn(&Shape::draw));
}

因此,mem_fn可以被看作从面向对象调用风格到函数式调用风格的一种映射
通常,lambda是比绑定器更简单也更通用的替代方案:

void draw_all(vector<Shape *>& v)
{
	for_each(v.begin(), v.end(), [](Shape *p){p->draw();});
}

function

如果要将bind的结果赋予一个特定类型的变量,可以使用标准库类型function。通过指明返回类型和参数类型来说明一个function:

std::function<int(void)> f0 = std::bind(func0);

标准库function是一种类型,它可以保存你能用调用运算符()调用的任何对象。即,一个function类型对象就是一个函数对象。这对回调、将操作作为参数传递等机制非常有用。


http://www.niftyadmin.cn/n/5863982.html

相关文章

C#实现Modbus TCP 通讯测试软件

C#实现Modbus TCP 通讯测试软件&#xff0c;源码&#xff0c;包括读写功能。 文件列表 WindowsFormsApplication6/WindowsFormsApplication6.sln , 1041 WindowsFormsApplication6/WindowsFormsApplication6.v12.suo , 39936 WindowsFormsApplication6/WindowsFormsApplicati…

python使用httpx_sse调用sse流式接口对响应格式为application/json的错误信息的处理

目录 问题描述方案 问题描述 调用sse流式接口使用httpx_sse的方式 import httpxfrom httpx_sse import connect_sse# 省略无关代码try:with httpx.Client() as client:with connect_sse(client, "GET", url, paramsparam) as event_source:clear_textbox(response_t…

Pytorch使用手册-音频数据增强(专题二十)

音频数据增强 torchaudio 提供了多种方式来增强音频数据。 在本教程中,我们将介绍一种应用效果、滤波器、RIR(房间脉冲响应)和编解码器的方法。 最后,我们将从干净的语音合成带噪声的电话语音。 import torch import torchaudio import torchaudio.functional as Fprin…

创建第一个 Maven 项目(二)

六、添加依赖 在 Maven 项目开发过程中&#xff0c;添加依赖是一项常见且关键的操作。通过添加依赖&#xff0c;我们可以引入项目所需的各种库和框架&#xff0c;极大地扩展项目的功能。接下来&#xff0c;我们将以 JUnit 依赖为例&#xff0c;详细介绍如何在 Maven 项目中添加…

保姆级! 本地部署DeepSeek-R1大模型 安装Ollama Api 后,Postman本地调用 deepseek

要在Postman中访问Ollama API并调用DeepSeek模型&#xff0c;你需要遵循以下步骤。首先&#xff0c;确保你有一个有效的Ollama服务器实例运行中&#xff0c;并且DeepSeek模型已经被加载。 可以参考我的这篇博客 保姆级&#xff01;使用Ollama本地部署DeepSeek-R1大模型 并java…

【Java八股文】11-分布式及场景面试篇

【Java八股文】11-分布式及场景面试篇 消息队列你的项目为什么要用消息队列&#xff1f;你说说 Kafka 为什么是高性能的&#xff1f;kafka的使用场景&#xff0c;是否有消息丢失的情况RocketMQ、Kafka 和 RabbitMQRabbitMq怎么消息被消费项目中MQ是怎么用的&#xff1f;MQ消息可…

基础dp——动态规划

目录 一、什么是动态规划&#xff1f; 二、动态规划的使用步骤 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 三、试题讲解 1.最小花费爬楼梯 2.下降路径最小和 3.解码方法 一、什么是动态规划&#xff1f; 动态规划&#xff08;Dynamic Programming&…

[通俗易懂C++]:指针和const

之前的文章有说过,使用指针我们可以改变指针指向的内容(通过给指针赋一个新的地址)或者改变被保存地址的值(通过给解引用指针赋一个新值): int main() {int x { 5 }; // 创建一个整数变量 x&#xff0c;初始值为 5int* ptr { &x }; // 创建一个指针 ptr&#xff0c;指向 …