C++20中的std::bind_front使用及原理分析

devtools/2025/3/5 5:40:30/

目录

1.简介

2.使用

2.1.绑定普通函数

2.2.绑定成员函数

2.3.与STL算法搭配使用

3.实现原理

4.注意事项

5.总结


1.简介

C++中的std::bind深入剖析-CSDN博客

   std::bind_front是C++20 新引入的一个函数模板。它住在<functional>这个头文件里,和我们熟悉的std::bind有点像,但又有着自己独特的魅力和功能。std::bind_front的主要任务,就是把一个可调用对象(这个可调用对象可以是普通函数、函数对象,也可以是成员函数)和一系列参数绑在一起。这里有个小特点,这些绑上去的参数,是从左到右,依次填到原函数参数列表的前面部分。绑好之后,就会生成一个新的可调用对象。当我们调用这个新对象的时候,它会先用上之前绑好的前几个参数,再结合我们调用时传进去的参数,去调用原来的那个可调用对象。这就好比组装一台复杂的机器,我们提前把一部分固定的零件安装好,之后要用这台机器的时候,只需要再装上剩下的零件,它就能正常运转啦。

template< class F, class... Args >
constexpr /*unspecified*/ bind_front( F&& f, Args&&... args );
  • F:可调用对象的类型,如函数、函数指针、成员函数指针、函数对象等。
  • Args:要预先绑定到可调用对象 f 前面的参数类型。

   工作原理

   std::bind_front 返回一个新的可调用对象,该对象在调用时会将预先绑定的参数传递给原始的可调用对象,并且可以在调用时提供剩余的参数。

2.使用

2.1.绑定普通函数

#include <iostream>
#include <functional>// 普通函数
int add(int a, int b) {return a + b;
}int main() {// 使用 std::bind_front 绑定第一个参数为 3auto addThree = std::bind_front(add, 3);// 调用绑定后的函数对象,只需要提供剩余的参数int result = addThree(5);std::cout << "Result: " << result << std::endl;return 0;
}

在这个示例中,std::bind_front 绑定了 add 函数的第一个参数为 3,返回一个新的可调用对象 addThree。调用 addThree 时,只需要提供第二个参数,它会自动将预先绑定的 3 作为第一个参数传递给 add 函数。

2.2.绑定成员函数

#include <iostream>
#include <functional>class Calculator {
public:int multiply(int a, int b) {return a * b;}
};int main() {Calculator calc;// 使用 std::bind_front 绑定成员函数和对象实例auto multiplyByTwo = std::bind_front(&Calculator::multiply, &calc, 2);// 调用绑定后的函数对象,只需要提供剩余的参数int result = multiplyByTwo(4);std::cout << "Result: " << result << std::endl;return 0;
}

在这个示例中,std::bind_front 绑定了 Calculator 类的成员函数 multiply、对象实例 calc 以及第一个参数 2,返回一个新的可调用对象 multiplyByTwo。调用 multiplyByTwo 时,只需要提供第二个参数,它会自动调用 calc 对象的 multiply 成员函数,并将预先绑定的 2 作为第一个参数传递。

2.3.与STL算法搭配使用

   std::bind_front还有个超厉害的本事,就是能和标准模板库(STL)里的算法配合得默契十足,发挥出更大的威力。比如说,我们有一个整数向量vector<int>,现在想找出这个向量里所有大于某个固定值的元素个数。这时候,我们就可以借助std::count_if算法std::bind_front来实现,看下面的代码:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>int main() {std::vector<int> numbers = {1, 5, 3, 7, 4, 9, 6};int fixedValue = 5;// 使用std::bind_front结合std::count_if算法auto count = std::count_if(numbers.begin(), numbers.end(), std::bind_front(std::greater<int>(), fixedValue));std::cout << "Number of elements greater than " << fixedValue << " is: " << count << std::endl;return 0;
}

在这个例子里,std::bind_front(std::greater<int>(), fixedValue)创建了一个新的可调用对象。这个新对象的作用就是判断一个数是不是大于fixedValue。然后std::count_if算法就利用这个新的可调用对象,去统计向量numbers里大于fixedValue的元素个数。这种搭配使用的方式,让代码既简洁又高效,充分展示了C++ 标准库的强大之处,是不是感觉像打开了新世界的大门。

3.实现原理

以vs2019的源码实现为例:

// FUNCTION TEMPLATE bind_front
template <class _Fx, class... _Types>
_NODISCARD constexpr auto bind_front(_Fx&& _Func, _Types&&... _Args) {static_assert(is_constructible_v<decay_t<_Fx>, _Fx>,"std::bind_front requires the decayed callable to be constructible from an undecayed callable");static_assert(is_move_constructible_v<decay_t<_Fx>>,"std::bind_front requires the decayed callable to be move constructible");static_assert(conjunction_v<is_constructible<decay_t<_Types>, _Types>...>,"std::bind_front requires the decayed bound arguments to be constructible from undecayed bound arguments");static_assert(conjunction_v<is_move_constructible<decay_t<_Types>>...>,"std::bind_front requires the decayed bound arguments to be move constructible");return _Front_binder<decay_t<_Fx>, decay_t<_Types>...>(_STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...);
}

首先用static_assert编译时检查传入参数的合法性,然后直接返回一个配接器_Front_binder:

// CLASS TEMPLATE _Front_binder
template <class _Fx, class... _Types>
class _Front_binder { // wrap bound callable object and arguments
private:using _Seq = index_sequence_for<_Types...>;_Compressed_pair<_Fx, tuple<_Types...>> _Mypair;_STL_INTERNAL_STATIC_ASSERT(is_same_v<_Fx, decay_t<_Fx>>);_STL_INTERNAL_STATIC_ASSERT((is_same_v<_Types, decay_t<_Types>> && ...));public:template <class _FxInit, class... _TypesInit,enable_if_t<sizeof...(_TypesInit) != 0 || !is_same_v<remove_cvref_t<_FxInit>, _Front_binder>, int> = 0>constexpr explicit _Front_binder(_FxInit&& _Func, _TypesInit&&... _Args): _Mypair(_One_then_variadic_args_t{}, _STD forward<_FxInit>(_Func), _STD forward<_TypesInit>(_Args)...) {}template <class... _Unbound>constexpr auto operator()(_Unbound&&... _Unbargs) & noexcept(noexcept(_Call_front_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)))-> decltype(_Call_front_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)) {return _Call_front_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...);}template <class... _Unbound>constexpr auto operator()(_Unbound&&... _Unbargs) const& noexcept(noexcept(_Call_front_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)))-> decltype(_Call_front_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)) {return _Call_front_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...);}template <class... _Unbound>constexpr auto operator()(_Unbound&&... _Unbargs) && noexcept(noexcept(_Call_front_binder(_Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)))-> decltype(_Call_front_binder(_Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)) {return _Call_front_binder(_Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...);}template <class... _Unbound>constexpr auto operator()(_Unbound&&... _Unbargs) const&& noexcept(noexcept(_Call_front_binder(_Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)))-> decltype(_Call_front_binder(_Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)) {return _Call_front_binder(_Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...);}
};

事先保存好传入的各个参数,重载operator()操作符,生成一个_Call_front_binder:

// FUNCTION TEMPLATE _Call_front_binder
template <size_t... _Ix, class _Cv_FD, class _Cv_tuple_TiD, class... _Unbound>
constexpr auto _Call_front_binder(index_sequence<_Ix...>, _Cv_FD&& _Obj, _Cv_tuple_TiD&& _Tpl,_Unbound&&... _Unbargs) noexcept(noexcept(_STD invoke(_STD forward<_Cv_FD>(_Obj),_STD get<_Ix>(_STD forward<_Cv_tuple_TiD>(_Tpl))..., _STD forward<_Unbound>(_Unbargs)...)))-> decltype(_STD invoke(_STD forward<_Cv_FD>(_Obj), _STD get<_Ix>(_STD forward<_Cv_tuple_TiD>(_Tpl))...,_STD forward<_Unbound>(_Unbargs)...)) {return _STD invoke(_STD forward<_Cv_FD>(_Obj), _STD get<_Ix>(_STD forward<_Cv_tuple_TiD>(_Tpl))...,_STD forward<_Unbound>(_Unbargs)...);
}

走到这一步就比较熟悉了,依然是调用std::invoke实现std::bind_front的功能。

C++17之std::invoke: 使用和原理探究(全)-CSDN博客

4.注意事项

虽然std::bind_front功能超级强大,但在使用的时候,也有一些地方需要我们特别留意。

1)首先,因为它创建了新的可调用对象,多多少少会增加一点代码的复杂性,运行的时候也可能会多占点资源。特别是在那些对性能要求极高的关键代码部分,我们得好好掂量掂量,看看使用std::bind_front会不会对性能产生不好的影响。

2)其次,在绑定成员函数的时候,一定要保证绑定的类实例,在新的可调用对象的整个生命周期里都是有效的。要是不小心让类实例提前“下岗”了,那新的可调用对象在调用成员函数的时候,就会出大问题,比如空指针引用之类的错误,这可是编程中的一颗“定时炸弹”💣,千万要小心。

3)如果预先绑定的参数是大对象,并且是按值传递的,那么在创建 std::bind_front 包装器对象时会发生参数的复制,这会带来一定的性能开销。可以通过使用 std::ref 或 std::cref 按引用传递参数来避免复制。

std::ref和std::cref的使用和原理分析-CSDN博客

4)当我们使用多个std::bind_front嵌套,或者把它和其他复杂的函数对象组合在一起的时候,代码的可读性可能会大打折扣。所以,在写代码的时候,一定要尽量保持代码结构清晰明了,别为了追求功能强大,把代码搞得太复杂,到最后自己都看不懂了。

5.总结

与其他替代方案的性能比较

1. 与 std::bind 比较

  • std::bind_front 通常比 std::bind 性能更好,因为它避免了使用占位符带来的参数重排和额外的类型推导开销。std::bind 在处理复杂的参数绑定情况时,可能会生成更复杂的代码,导致性能下降。

2. 与 lambda 表达式比较

  • 简单场景:在简单的参数绑定场景下,std::bind_front 和 lambda 表达式的性能相近。编译器对两者都可以进行一定程度的优化。
  • 复杂场景:如果需要更复杂的逻辑,例如捕获多个变量、进行条件判断等,lambda 表达式可能更灵活,但也可能会引入更多的代码复杂性。而 std::bind_front 专注于参数绑定,代码相对简洁,性能可能更稳定。

总体而言,std::bind_front 在大多数情况下具有较好的性能,尤其是在简单的参数绑定场景下。但在实际使用中,还需要根据具体的可调用对象和参数情况来评估其性能。


http://www.ppmy.cn/devtools/164657.html

相关文章

玩转ChatGPT:Claude 3.7 Sonnet进行数据分析(分类)

一、写在前面 Claude 3.7 Sonnet刚刚发布&#xff0c;号称是全球第一个混合推理模型。 我们直接来测试一下它的数据分析能力。 首先&#xff0c;是解决机器学习分类问题。 二、开测 &#xff08;1&#xff09;描述性分析 上传csv数据文件&#xff0c;写下咒语&#xff1a;…

大模型工程师学习日记(十一):FAISS 高效相似度搜索和密集向量聚类的库

Facebook AI Similarity Search (Faiss /Fez/) 是一个用于高效相似度搜索和密集向量聚类的库。它包含了在任意大小的向量集合中进行搜索的算法&#xff0c;甚至可以处理可能无法完全放入内存的向量集合。它还包含用于评估和参数调整的支持代码。 Faiss 官方文档&#xff1a;We…

Python 绘制迷宫游戏,自带最优解路线

1、需要安装pygame 2、上下左右移动&#xff0c;空格实现物体所在位置到终点的路线&#xff0c;会有虚线绘制。 import pygame import random import math# 迷宫单元格类 class Cell:def __init__(self, x, y):self.x xself.y yself.walls {top: True, right: True, botto…

PyCharm接入本地部署DeepSeek 实现AI编程!【支持windows与linux】

今天尝试在pycharm上接入了本地部署的deepseek&#xff0c;实现了AI编程&#xff0c;体验还是很棒的。下面详细叙述整个安装过程。 本次搭建的框架组合是 DeepSeek-r1:1.5b/7b Pycharm专业版或者社区版 Proxy AI&#xff08;CodeGPT&#xff09; 首先了解不同版本的deepsee…

【计算机网络入门】初学计算机网络(九)

目录 1.令牌传递协议 2. 局域网&IEEE802 2.1 局域网基本概念和体系结构 3. 以太网&IEEE802.3 3.1 MAC层标准 3.1.1 以太网V2标准 ​编辑 3.2 单播广播 3.3 冲突域广播域 4. 虚拟局域网VLAN 1.令牌传递协议 先回顾一下令牌环网技术&#xff0c;多个主机形成…

MyBatis的关联映射

前言 在实际开发中&#xff0c;对数据库的操作通常会涉及多张表&#xff0c;MyBatis提供了关联映射&#xff0c;这些关联映射可以很好地处理表与表&#xff0c;对象与对象之间的的关联关系。 一对一查询 步骤&#xff1a; 先确定表的一对一关系确定好实体类&#xff0c;添加关…

vue3+nuxt中监听sessionStorage.setItem,数据发生变化动态获取响应

在写项目时&#xff0c;遇见一个未读消息的功能&#xff0c;当消息已读时&#xff0c;减少一条未读消息提示&#xff0c;需要实现当sessionStorage.setItem存储的数据发生改变时&#xff0c;响应式的获取并显示&#xff0c;但是由于sessionStorage 的 setItem 操作本身并不会触…

深度学习-137-LangGraph之应用实例(六)构建RAG问答系统带条件边分支

文章目录 1 大语言模型2 带分支的RAG系统2.1 处理文本构建Document2.2 向量存储2.3 自定义工具2.4 创建图2.4.1 预构建的MessagesState2.4.2 编排图2.4.3 可视化图2.5 测试调用3 参考附录使用langgraph框架构建一个带有条件分支的智能问答系统。创建一个能够从网页文档中提取信…