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

C++11: STL之bind

C++11: STL之bind

  • 引言
  • 可调用对象的绑定
    • 绑定普通函数
    • 绑定静态函数
    • 绑定类成员函数
    • 绑定仿函数
    • 绑定Lambda
  • 占位符std::placeholders的应用
    • 嵌套绑定
    • 参数重排序
    • 结合 STL 算法
    • 占位符传递到嵌套函数
    • 混合占位符与默认值
    • 复杂占位符组合
  • std::bind的原理
    • std::bind 的设计思路
    • 简化实现示例
  • 总结

引言

std::bind 是 C++11 引入的一种函数适配器,能够将一个可调用对象(如函数、成员函数、仿函数或 lambda 表达式)与其支持的部分(或全部)参数绑定生成一个新的可调用对象。基于std::bind,开发者可调整目标函数的参数列表顺序或绑定部分参数,这极大地增强了函数调用的灵活性。

std::bind 的函数声明位于头文件 ,其形式如下:

template< class F, class... Args > 
/* unspecified */ bind( F&& f, Args&&... args );

template< class R, class F, class... Args >
/* unspecified */ bind( F&& f, Args&&... args );

参数说明:
• F: 可调用对象(普通函数、静态函数、类成员函数、仿函数或 lambda)。
• Args: 要绑定的参数列表。可以传递具体值或占位符 std::placeholders::_1, std::placeholders::_2, … (表示待传入的参数)。

可调用对象的绑定

Bind可支持普通函数、静态函数、类成员函数、仿函数或 lambda等多种可以调用对象的绑定。

绑定普通函数

对于普通函数,我们可以直接利用 std::bind 绑定部分参数,进而生成一个新的可调用对象。例如:

#include <iostream>
#include <functional>

void printMessage(const std::string& message, int repeatCount) 
{
    for (int i = 0; i < repeatCount; ++i)
    {
        std::cout << message << std::endl;
    }
}

int main() 
{
    auto bindPrintMessage = std::bind(printMessage, "Hello, World!", 3);
    bindPrintMessage(); // 输出三行 "Hello, World!"

    return 0;
}

绑定静态函数

静态成员函数与普通函数在绑定方式上较为相似,可以直接进行绑定操作。

#include <iostream>
#include <functional>

class Logger 
{
public:
    static void logInfo(const std::string& message) 
    {
        std::cout << "[INFO]: " << message << std::endl;
    }
};

int main() 
{
    auto bindLogInfo = std::bind(&Logger::logInfo, "Application started");
    bindLogInfo(); // 输出: [INFO]: Application started

    return 0;
}

绑定类成员函数

当绑定类的成员函数时,需要额外传递对象实例的指针作为绑定的一部分。

#include <iostream>
#include <functional>

class Calculator 
{
public:
    void add(int a, int b) const 
    {
        std::cout << "Sum: " << (a + b) << std::endl;
    }
};

int main() 
{
    Calculator calculator;
    auto bindAdd = std::bind(&Calculator::add, &calculator, 5, 7);
    bindAdd(); // 输出: Sum: 12

    return 0;
}

绑定仿函数

仿函数是通过重载 operator() 实现的对象行为,亦可以与 std::bind 结合。

#include <iostream>
#include <functional>

struct Multiply 
{
    void operator()(int a, int b) const 
    {
        std::cout << "Product: " << (a * b) << std::endl;
    }
};

int main() 
{
    Multiply multiply;
    auto bindMultiply = std::bind(multiply, 4, 5);
    bindMultiply(); // 输出: Product: 20

    return 0;
}

绑定Lambda

std::bind 也可以绑定 lambda 表达式,这种方式很灵活。

#include <iostream>
#include <functional>

int main() 
{
    auto divide = [](double a, double b) {
        if (b != 0) 
        {
            std::cout << "Result: " << (a / b) << std::endl;
        } 
        else 
        {
            std::cout << "Error: Division by zero!" << std::endl;
        }
    };

    auto bindDivide = std::bind(divide, 10.0, 2.0);
    bindDivide(); // 输出: Result: 5

    return 0;
}

占位符std::placeholders的应用

std::placeholders 是 std::bind 中的重要组成部分,允许用户在绑定函数时动态指定参数位置、调整参数顺序或实现嵌套绑定。通过使用std::placeholders::_1, std::placeholders::_2 等占位符,可以构建灵活的函数调用方式,尤其适合处理动态输入和函数组合逻辑。

嵌套绑定

使用 std::bind将一个函数绑定为另一个函数的参数,我们称之为嵌套绑定。

#include <iostream>
#include <functional>

void multiplyAndPrint(int a, int b) 
{
    std::cout << "Product: " << (a * b) << std::endl;
}

void wrapperFunction(std::function<void(int)> func, int value) 
{
    func(value);
}

int main() 
{
    // 绑定 multiplyAndPrint 的第一个参数为 5,第二个参数为动态传递的值
    auto multiplyByFive = std::bind(multiplyAndPrint, 5, std::placeholders::_1);

    // 绑定 wrapperFunction,将 multiplyByFive 作为其固定参数
    auto bindWrapper = std::bind(wrapperFunction, multiplyByFive, std::placeholders::_1);

    // 动态传递参数
    bindWrapper(10); // 输出: Product: 50

    return 0;
}

参数重排序

通过占位符调整参数的传递顺序。

#include <iostream>
#include <functional>

void calculate(int a, int b, int c) 
{
    std::cout << "Result: " << (a + b * c) << std::endl;
}

int main() 
{
    // 调整参数顺序:
    //  1. std::placeholders::_3表示将调用的第二个参数传递给binder的第三个参数,
    //  2. std::placeholders::_2表示将调用的第三个参数传递给binder的第二个参数
    auto reorderedCalculate = std::bind(calculate, std::placeholders::_1, std::placeholders::_3, std::placeholders::_2);

    reorderedCalculate(2, 3, 4); // 实际调用: calculate(2, 4, 3),输出: Result: 14

    return 0;
}

结合 STL 算法

将 std::bind 与 STL 算法结合,在容器中处理动态参数。

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>

void printIfGreater(int value, int threshold) 
{
    if (value > threshold) 
    {
        std::cout << value << " ";
    }
}

int main() 
{
    std::vector<int> numbers = {1, 3, 5, 7, 9, 11};

    // 绑定动态阈值
    auto bindPrintIfGreater = std::bind(printIfGreater, std::placeholders::_1, 5);

    std::for_each(numbers.begin(), numbers.end(), bindPrintIfGreater);
    // 输出: 7 9 11

    return 0;
}

占位符传递到嵌套函数

占位符可以在多层绑定中传递,适用于复杂嵌套调用场景。

#include <iostream>
#include <functional>

void outerFunction(int x, std::function<void(int)> innerFunc) 
{
    std::cout << "Outer: " << x << std::endl;
    innerFunc(x + 1);
}

void innerFunction(int y) 
{
    std::cout << "Inner: " << y << std::endl;
}

int main() 
{
    // 绑定 outerFunction 的第一参数为动态传递,第二参数绑定 innerFunction
    auto bindOuter = std::bind(outerFunction, std::placeholders::_1, std::bind(innerFunction, std::placeholders::_1));

    bindOuter(10); // 输出:
                   // Outer: 10
                   // Inner: 11

    return 0;
}

混合占位符与默认值

结合固定值和动态参数。

#include <iostream>
#include <functional>

void greet(const std::string& greeting, const std::string& name, int repeat) 
{
    for (int i = 0; i < repeat; ++i) 
    {
        std::cout << greeting << ", " << name << "!" << std::endl;
    }
}

int main() 
{
    // 固定 greeting 为 "Hello",repeat 为 2,动态传递 name
    auto bindGreet = std::bind(greet, "Hello", std::placeholders::_1, 2);

    bindGreet("Alice"); // 输出:
                        // Hello, Alice!
                        // Hello, Alice!

    bindGreet("Bob");   // 输出:
                        // Hello, Bob!
                        // Hello, Bob!

    return 0;
}

复杂占位符组合

结合静态绑定、动态参数和参数顺序调整,展示复杂调用逻辑。

#include <iostream>
#include <functional>

void processValues(int x, int y, int z) 
{
    std::cout << "Processed: " << (x + y - z) << std::endl;
}

int main() 
{
    // 固定 x 为 10,z 为动态传递,y 和 z 的顺序调整
    auto bindProcess = std::bind(processValues, 10, std::placeholders::_2, std::placeholders::_1);

    bindProcess(5, 20); // 实际调用: processValues(10, 20, 5)
                        // 输出: Processed: 25

    return 0;
}

std::bind的原理

std::bind 的设计思路

std::bind 的设计基于 函数对象封装 和 参数转发 的思想。 std::bind 会将目标函数及其参数封装为一个可调用对象(一般是一个类), 该可调用对象重载了 operator(),从而能够模拟函数调用;对于绑定的参数,std::bind 会存储其值,对于动态传递的参数,占位符(如 std::placeholders::_1、std::placeholders::_2)会在调用时将实际参数映射到正确的位置;std::bind 是高度模板化的,其实现依赖于 完美转发 和 类型擦除,从而能够适配不同类型的函数及参数。

简化实现示例

#include <tuple>
#include <utility>
#include <iostream>

// 占位符实现
template <int Index>
struct Placeholder
{
    template <typename... CallArgs>
    auto operator()(CallArgs&&... callArgs) const
    {
        return std::get<Index>(std::forward_as_tuple(callArgs...));
    }
};

// 定义全局占位符
constexpr Placeholder<0> _1{};
constexpr Placeholder<1> _2{};

// 简化版 Bind 实现
template <typename Func, typename... BoundArgs>
class Bind
{
public:
    Bind(Func f, BoundArgs... boundArgs)
    : m_func(std::move(f)), m_boundArgs(std::make_tuple(std::forward<BoundArgs>(boundArgs)...))
    {
    }

    template <typename... CallArgs>
    auto operator()(CallArgs&&... callArgs)
    {
        return invoke(std::index_sequence_for<BoundArgs...>{}, std::forward<CallArgs>(callArgs)...);
    }

private:
    Func m_func;                          // 存储目标函数
    std::tuple<BoundArgs...> m_boundArgs; // 存储绑定的参数

    // 调用目标函数
    template <std::size_t... Indexes, typename... CallArgs>
    auto invoke(std::index_sequence<Indexes...>, CallArgs&&... callArgs)
    {
        return m_func(resolve(std::get<Indexes>(m_boundArgs), callArgs...)...);
    }

    // 如果参数是值,则直接返回
    template <typename Arg, typename... CallArgs>
    static auto resolve(Arg&& arg, CallArgs&&...)
    {
        return std::forward<Arg>(arg);
    }

    // 如果参数是 _1,占位符对应第一个动态传入的参数
    template <typename... CallArgs>
    static auto resolve(const Placeholder<0>&, CallArgs&&... callArgs)
    {
        return std::get<0>(std::forward_as_tuple(callArgs...));
    }

    // 如果参数是 _2,占位符对应第二个动态传入的参数
    template <typename... CallArgs>
    static auto resolve(const Placeholder<1>&, CallArgs&&... callArgs)
    {
        return std::get<1>(std::forward_as_tuple(callArgs...));
    }
};

// 辅助函数
template <typename Func, typename... Args>
auto bind(Func&& func, Args&&... args) 
{
    return Bind<Func, Args...>(std::forward<Func>(func), std::forward<Args>(args)...);
}

// 测试普通函数
int add(int a, int b)
{
    return a + b;
}

int main()
{
    // 绑定 add 函数,第二个参数固定为 10
    auto boundAdd = bind(add, _1, 10);
    std::cout << "Result: " << boundAdd(5) << std::endl; // 输出: Result: 15

    // 绑定 add 函数,两个参数动态传入
    auto dynamicAdd = bind(add, _2, _1);
    std::cout << "Result: " << dynamicAdd(10, 5) << std::endl; // 输出: Result: 15

    return 0;
}

总结

std::bind 是 C++11 提供的一个强大工具,用于灵活地处理函数调用。它能够绑定普通函数、成员函数、仿函数或 Lambda 表达式的部分参数,并通过占位符 std::placeholders 动态传递其他参数,从而在参数重排序、函数适配以及嵌套调用中发挥巨大作用。此外,std::bind 可以很好地与标准库算法结合,适用于高阶函数和动态参数传递的场景。

在使用 std::bind 时,需要注意以下几点:

  • 灵活性与复杂性:std::bind 提供了极大的灵活性,但在复杂绑定和嵌套场景中可能会使代码可读性下降。对于简单任务,优先考虑使用 Lambda 表达式。
  • 性能影响:std::bind 会生成额外的中间函数对象,在性能敏感的场景下,需要注意其开销。
  • 占位符顺序:通过 std::placeholders::_n 绑定动态参数时,需明确顺序映射关系,避免参数传递错误。

随着现代 C++ 标准的演化,std::bind 的使用场景逐渐被 Lambda 表达式取代,后者提供了更直观的语法和性能优化。然而,在某些需要函数适配器的场景中,std::bind 仍然是一个值得掌握的工具。

通过对 std::bind 的原理和实现机制的解析,可以更好地理解其设计思路和应用方式,这对深入学习现代 C++ 的函数式编程范式也具有重要意义。


http://www.kler.cn/a/409400.html

相关文章:

  • 秋招面试基础总结,Java八股文基础(串联知识),四万字大全
  • 汽车HiL测试:利用TS-GNSS模拟器掌握硬件性能的仿真艺术
  • EXISTS 和 IN 的使用方法、特性及查询效率比较
  • android-sdk 安装脚本
  • 快速获取镜像包的方法
  • 11 —— 打包模式的应用
  • 【MySQL】sql注入相关内容
  • 【开源风云】从若依系列脚手架汲取编程之道(八)
  • C#里怎么样使用正则表达式?
  • 动态规划—课堂笔记=>背包问题(2)
  • 东胜物流软件 GetDataListCA SQL注入漏洞复现
  • Laravel对接SLS日志服务
  • 如何快速将Excel数据导入到SQL Server数据库
  • 界面控件DevExpress WPF中文教程:网格视图数据布局的列和卡片字段
  • C++中定义类型名的方法
  • 【Golang】——Gin 框架与数据库集成详解
  • Python的tkinter如何把日志弄进文本框(Text)
  • 大事件管理系统项目总结(上)
  • 【Vscode】不同系统快捷键
  • 论防火墙对网络安全的重要性
  • 【大数据学习 | Spark-Core】Spark提交及运行流程
  • Oracle 执行计划查看方法汇总及优劣对比
  • 信息收集ip测活-Python脚本编写
  • Java零拷贝一步曲——Linux 中的零拷贝技术
  • C++ Qt 识别U盘/串口
  • 传输控制协议(TCP)和用户数据报协议(UDP)