当前位置: 首页 > article >正文

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

目录

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 绑定第一个参数为 3
    auto 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.kler.cn/a/571424.html

相关文章:

  • 小米 SU7 Ultra:科技与性能的极致融合,FPC 隐匿的关键力量【新立电子】
  • Windows逆向工程入门之MASM STRUCT
  • Visual Studio Code 如何编写运行 C、C++ 程序
  • 多线程与异步任务处理(二):Kotlin协程
  • 【Linux篇】第一个系统程序 - 进度条
  • 【Laplacian边缘检测详解】
  • Compose Multiplatform开发记录之文件选择器封装
  • 三方库总结
  • 【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(成功版)
  • 鸿蒙与DeepSeek深度整合:构建下一代智能操作系统生态
  • 【leetcode hot 100 189】轮转数组
  • 从零开始学习Slam--数学概念
  • 《Operating System Concepts》阅读笔记:p180-p187
  • 蓝桥杯 2020 国赛 阶乘约数
  • 自学微信小程序的第十天
  • [liorf_localization_imuPreintegration-2] process has died
  • python全栈-Linux基础
  • 【Linux】进程信号——信号保存和信号捕捉
  • 【大模型】什么是蒸馏版大模型
  • 【零基础到精通Java合集】第二十三集:G1收集器深度解析