跳到主要内容C++ std::function 包装器与 bind 绑定详解 | 极客日志C++
C++ std::function 包装器与 bind 绑定详解
介绍 C++11 引入的 std::function 模板类及其用法。std::function 可封装普通函数、Lambda、仿函数等,实现类型擦除统一接口。通过实际案例展示其在逆波兰表达式求值中的应用。此外,详细讲解了 std::bind 如何修改参数顺序、固定参数值以及绑定类成员函数,增强代码灵活性与复用性。
随缘889 浏览 1. 什么是 std::function?
std::function 是 C++11 引入的一个模板类,用于封装任何可调用对象(如普通函数、Lambda 表达式、函数指针、成员函数指针或函数对象等)。它允许你存储一个可调用对象,并在需要时调用它。这使得我们可以更加灵活地编写代码,特别是在需要传递回调函数或异步任务时,std::function 显得尤为重要。
std::function 是通过类型擦除实现的,它可以在运行时动态地将不同类型的可调用对象转化为统一的接口。简单来说,它允许你用一个通用的对象来代替不同类型的函数或函数指针。
2. function 包装器的原型
std::function 在头文件 中定义。
template <class T> function;
template <class Ret, class... Args> class function<Ret(Args...)>;
模板参数说明:
- Ret: 被调用函数的返回类型
- Args...: 被调用函数的形参
3. 使用 function 封装不同类型的函数对象
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void func(int n) {
cout << "普通函数:" << n << endl;
}
struct Func {
void operator()(int n) {
cout << "仿函数:" << n << endl;
}
};
lambda = []( n) { cout << << n << endl; };
{
function<()> f;
f = func;
();
f = ();
();
f = lambda;
();
;
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
auto
int
"Lambda 表达式:"
int main()
void
int
f
10
Func
f
20
f
30
return
0
代码分析
我们定义了三个不同类型的函数:一个普通函数 func、一个仿函数 Func 和一个 Lambda 表达式 lambda。
然后,使用 std::function<void(int)> 来封装这三种不同类型的函数对象。
通过调用包装后的 f,我们可以统一的方式执行这些不同的函数对象(适配器)。
这种方式使得我们能够将多种类型的可调用对象统一为一个接口,方便管理和使用。
4. 实际应用
以逆波兰表达式求值问题为例,利用 function 包装器可以简化操作符的映射处理。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for (auto& to : tokens) {
if (to == "+" || to == "-" || to == "*" || to == "/") {
int right = st.top(); st.pop();
int left = st.top(); st.pop();
switch (to[0]) {
case '+': st.push(left + right); break;
case '-': st.push(left - right); break;
case '*': st.push(left * right); break;
case '/': st.push(left / right); break;
}
} else {
st.push(stoi(to));
}
}
return st.top();
}
};
class Solution {
public:
int evalRPN(vector<string>& tokens) {
unordered_map<string, function<int(int, int)>> hash = {
{"+", [](int x, int y) -> int { return x + y; }},
{"-", [](int x, int y) -> int { return x - y; }},
{"*", [](int x, int y) -> int { return x * y; }},
{"/", [](int x, int y) -> int { return x / y; }}
};
stack<int> s;
for (auto str : tokens) {
if (str != "+" && str != "-" && str != "*" && str != "/") {
s.push(stoi(str));
} else {
int y = s.top(); s.pop();
int x = s.top(); s.pop();
s.push(hash[str](x, y));
}
}
return s.top();
}
};
5. bind 绑定:修改参数传递顺序和数量
bind 是 C++ 标准库中的一个函数模板,它允许我们对函数参数进行预先绑定或重新排列,从而生成一个新的可调用对象。bind 的强大之处在于,它不仅能够指定某些参数的固定值,还能改变参数传递的顺序,极大地提高了灵活性。
template <class Fn, class... Args> bind(Fn&& fn, Args&&... args);
fn 是传递的函数对象,args 是传给函数的可变参数包,这里使用了万能引用(引用折叠),使其在进行模板类型推导时,既能引用左值,也能引用右值。
2.1 使用 bind 绑定修改参数传递顺序
#include <iostream>
#include <functional>
using namespace std;
void Func(int a, int b) {
cout << "Func: " << a << " " << b << endl;
}
int main() {
Func(10, 20);
auto RFunc = bind(Func, std::placeholders::_2, std::placeholders::_1);
RFunc(10, 20);
return 0;
}
bind(Func, std::placeholders::_2, std::placeholders::_1) 通过 placeholders::_1 和 placeholders::_2 指定了新的参数顺序,即将原本的第二个参数和第一个参数交换。
当我们调用 RFunc(10, 20) 时,实际上是将 20 作为第一个参数,10 作为第二个参数传递给 Func。
这种参数顺序的改变,在一些特定的应用场景下非常有用,特别是在函数签名不一致时,可以方便地进行适配。
2.2 bind 绑定:指定特定参数
bind 还可以用于指定函数的某些参数为固定值,从而减少后续调用时需要传递的参数个数。
#include <iostream>
#include <functional>
using namespace std;
void Func(int a, int b) {
cout << "Func: " << a << " " << b << endl;
}
int main() {
auto RFunc = bind(Func, 100, std::placeholders::_1);
RFunc(20);
return 0;
}
我们通过 bind(Func, 100, std::placeholders::_1) 将第一个参数绑定为固定值 100。
后续调用时,我们只需要传递第二个参数 20,bind 会自动将 100 作为第一个参数传递给 Func。
2.3 bind 绑定与类成员函数
bind 还可以用于绑定类成员函数。对于普通函数,绑定非常简单,但对于成员函数,我们需要额外注意如何传递类的对象或指针。
#include <iostream>
#include <functional>
using namespace std;
class Test {
public:
static void funcA(int val) {
cout << "静态成员函数 funcA: " << val << endl;
}
};
int main() {
auto RFunc = bind(&Test::funcA, std::placeholders::_1);
RFunc(10);
return 0;
}
对于静态成员函数,我们可以直接使用 &Test::funcA 来绑定。
bind 会自动处理函数的绑定,并返回一个新的可调用对象 RFunc,我们可以使用它来调用函数。
#include <iostream>
#include <functional>
using namespace std;
class Test {
public:
Test(int n) : _n(n) {}
void funcB(int val) {
cout << "非静态成员函数 funcB: " << val * _n << endl;
}
private:
int _n;
};
int main() {
Test t(10);
auto RFunc = bind(&Test::funcB, t, std::placeholders::_1);
RFunc(5);
return 0;
}
对于非静态成员函数,我们需要提供类的对象 t 作为参数来绑定。
bind 会将 t 与 &Test::funcB 结合,并生成一个新的可调用对象。
总结
通过 std::function 和 bind,C++ 提供了强大的函数包装和绑定功能,使得我们能够在不同类型的函数之间进行无缝切换、修改参数传递顺序以及绑定特定参数。这些工具极大地增强了代码的灵活性和可重用性,特别是在需要对多个不同函数进行统一管理时,它们提供了非常便捷的解决方案。在实际开发中,这些技巧不仅能帮助我们提升编程效率,还能让代码更加简洁和优雅。