目录
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
在大多数情况下具有较好的性能,尤其是在简单的参数绑定场景下。但在实际使用中,还需要根据具体的可调用对象和参数情况来评估其性能。