C++包装器
·
C++包装器
C++11主要引入了三个重要的包装器组件:
std::functionstd::bindstd::mem_fn
我们将逐一深入探讨。
1. std::function:通用函数包装器
原理与作用
原理:std::function 是一个类模板,它使用类型擦除(Type Erasure)技术。这意味着它可以在运行时存储、复制和调用任何可调用对象(Callable Object),只要该对象的签名与std::function模板参数所声明的函数签名匹配。
作用:
- 统一类型:为各种可调用实体(函数指针、函数对象、lambda表达式等)提供了一个统一的类型。这使得你可以编写接收多种形式回调函数的通用接口,而无需为每种类型编写重载版本。
- 增强灵活性:可以将回调函数作为参数传递、存储在容器中(如
std::vector<std::function<void()>>),或者作为类的成员变量,极大地增加了代码的灵活性。 - 运行时多态:类似于面向对象中的基类指针,但它是针对函数调用的多态,不需要继承体系。
可调用对象包括:
- 普通函数
- 函数指针
- Lambda表达式
std::bind生成的表达式- 重载了
operator()的类对象(仿函数)
代码示例与详解
#include <iostream>
#include <functional> // 必须包含此头文件
// 1. 普通函数
int add(int a, int b) {
return a + b;
}
// 2. 仿函数 (Function Object)
struct Multiply {
int operator()(int a, int b) {
return a * b;
}
};
// 3. 一个接收 std::function 作为参数的通用函数
void print_result(const std::string& name, std::function<int(int, int)> func, int a, int b) {
std::cout << name << "(" << a << ", " << b << ") = " << func(a, b) << std::endl;
}
int main() {
// 包装一个普通函数
std::function<int(int, int)> func1 = add;
print_result("add", func1, 3, 4); // 输出: add(3, 4) = 7
// 包装一个仿函数对象
Multiply multiply_obj;
std::function<int(int, int)> func2 = multiply_obj;
print_result("multiply", func2, 3, 4); // 输出: multiply(3, 4) = 12
// 包装一个Lambda表达式
std::function<int(int, int)> func3 = [](int a, int b) { return a - b; };
print_result("subtract", func3, 10, 4); // 输出: subtract(10, 4) = 6
// 存储于容器中
std::vector<std::function<int(int, int)>> functions = {add, multiply_obj, func3};
for (auto& f : functions) {
print_result("container", f, 5, 3);
}
// 输出:
// container(5, 3) = 8
// container(5, 3) = 15
// container(5, 3) = 2
return 0;
}
注意事项
- 当
std::function未包裹任何可调用对象时(即为空),调用它会抛出std::bad_function_call异常。可以使用if (func)或if (func != nullptr)来检查是否为空。 std::function会拷贝其包裹的可调用对象。如果希望避免拷贝,可以用std::ref/std::cref来包裹可调用对象,传递引用。MyBigFunctor big_obj; // 避免拷贝 big_obj,传递引用 std::function<void()> f = std::ref(big_obj);- 与直接调用或使用auto相比,
std::function可能有一定的性能开销(因为类型擦除和动态分配),但在大多数情况下可以忽略不计。
2. std::bind:绑定参数生成新的可调用对象
原理与作用
原理:std::bind是一个函数模板,它接受一个可调用对象和一些参数,并返回一个新的可调用对象。返回的对象会“记住”提供的参数,当你调用这个新对象时,它会将“记住的”参数和你新提供的参数一起传递给原始的可调用对象。
作用:
- 参数绑定(Partial Function Application):将可调用对象的某些参数固定为特定值,从而生成一个参数更少的新函数。
- 重新排列参数顺序:可以改变原始函数参数的顺序。
- 占位符(Placeholders):使用
std::placeholders::_1, _2, _3, ...来表示新函数调用时传入的参数位置。
代码示例与详解
#include <iostream>
#include <functional>
#include <string>
void print_values(int a, double b, const std::string& c) {
std::cout << "a=" << a << ", b=" << b << ", c=" << c << std::endl;
}
class MyClass {
public:
void member_func(int x, const std::string& msg) {
std::cout << "MyClass::member_func: x=" << x << ", msg=" << msg << std::endl;
}
static void static_func(int x) {
std::cout << "MyClass::static_func: x=" << x << std::endl;
}
};
int main() {
using namespace std::placeholders; // 对于 _1, _2, _3...
// 1. 绑定普通函数,固定部分参数
// 将 print_values 的第三个参数固定为 "Hello"
auto func1 = std::bind(print_values, _1, _2, "Hello");
// func1 现在只需要两个参数:_1 (对应a), _2 (对应b)
func1(10, 3.14); // 等价于 print_values(10, 3.14, "Hello")
// 2. 重新排列参数顺序
// 将 print_values 的参数顺序变为 (c, a, b)
auto func2 = std::bind(print_values, _2, _3, _1);
func2("World", 20, 6.28); // 等价于 print_values(20, 6.28, "World")
// 3. 绑定成员函数(非常重要!)
MyClass obj;
// 绑定非静态成员函数,第一个参数必须是该类的对象实例(或指针/引用)
// _1 表示新函数的第一个参数将是调用成员函数的对象
auto func3 = std::bind(&MyClass::member_func, _1, 42, "Bound");
func3(obj); // 等价于 obj.member_func(42, "Bound")
// 也可以将对象固定住
auto func4 = std::bind(&MyClass::member_func, &obj, _1, "Fixed Object");
func4(100); // 等价于 obj.member_func(100, "Fixed Object")
// 4. 绑定静态成员函数和重载运算符,与普通函数一样
auto func5 = std::bind(&MyClass::static_func, _1);
func5(123);
// 5. 绑定Lambda表达式或其他函数对象
auto lambda = [](int x, int y) { return x > y; };
auto func6 = std::bind(lambda, _1, 5); // 检查输入是否大于5
std::cout << std::boolalpha << func6(10) << std::endl; // 输出: true
return 0;
}
注意事项
- 使用
std::bind绑定非静态成员函数时,第一个绑定的参数必须是该类的对象实例、指针或引用。 - 绑定参数时,参数是按值传递的。如果需要按引用传递,必须使用
std::ref或std::cref。int value = 10; // 错误:bind按值捕获,修改的是副本 // auto bad_ref = std::bind(print_values, _1, _2, value); // 正确:使用std::ref按引用捕获 auto good_ref = std::bind(print_values, _1, _2, std::ref(value)); - 在C++11之后,Lambda表达式通常比
std::bind更具可读性和灵活性,应优先考虑使用Lambda。但在某些需要显式模板化或与旧代码交互的场景下,std::bind仍然有用。
3. std::mem_fn:生成成员函数指针的包装器
原理与作用
原理:std::mem_fn是一个函数模板,它接受一个成员函数指针,并返回一个可调用对象。这个可调用对象可以接受一个类的对象(或指针、引用)以及相应的参数,然后在其上调用绑定的成员函数。
作用:
- 简化成员函数调用:提供了一种统一的方式来调用成员函数,无论你是用对象、指针还是智能指针。
- 用于算法:特别适用于STL算法,因为算法通常接收一个可调用对象,而直接使用成员函数指针语法很繁琐。
代码示例与详解
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
class Number {
public:
Number(int v) : value(v) {}
void print() const {
std::cout << value << " ";
}
bool is_even() const {
return value % 2 == 0;
}
int value;
};
int main() {
std::vector<Number> numbers = {1, 2, 3, 4, 5};
std::vector<Number*> number_ptrs = {&numbers[0], &numbers[1], &numbers[2]};
// 1. 使用 std::mem_fn 生成包装器
auto print_func = std::mem_fn(&Number::print);
// 对对象调用
for (auto& num : numbers) {
print_func(num); // 输出: 1 2 3 4 5
}
std::cout << std::endl;
// 对指针调用
for (auto ptr : number_ptrs) {
print_func(ptr); // 输出: 1 2 3
}
std::cout << std::endl;
// 2. 在STL算法中使用
// 使用 std::mem_fn 判断每个元素是否为偶数
std::vector<bool> evens;
std::transform(numbers.begin(), numbers.end(),
std::back_inserter(evens),
std::mem_fn(&Number::is_even)); // 非常简洁
for (bool e : evens) {
std::cout << std::boolalpha << e << " "; // 输出: false true false true false
}
std::cout << std::endl;
// 3. 对比:如果不使用 std::mem_fn,在算法中会非常繁琐
std::vector<bool> odds;
// 需要使用Lambda来包装成员函数调用
std::transform(numbers.begin(), numbers.end(),
std::back_inserter(odds),
[](const Number& n) { return !n.is_even(); }); // 需要手动写Lambda
return 0;
}
与std::bind的差异和区别
| 特性 | std::mem_fn | std::bind |
|---|---|---|
| 主要目的 | 专门用于包装成员函数指针 | 通用绑定,可用于任何可调用对象和参数 |
| 语法简洁性 | 对于成员函数,语法更简洁 | 语法相对复杂,需要占位符 |
| 灵活性 | 较低,主要用于调用成员函数 | 极高,可以绑定任意参数,改变顺序 |
| 调用方式 | 自动适应对象、指针、引用 | 需要通过绑定的第一个参数来指定调用对象 |
简单来说,std::mem_fn是调用成员函数这一特定任务的“语法糖”,而std::bind是一个功能更强大的通用工具。 在只需要调用成员函数而不需要复杂参数绑定的场景下,std::mem_fn是更清晰的选择。
总结与对比
| 工具 | 核心思想 | 最佳适用场景 |
|---|---|---|
std::function | 类型擦除,统一包装 | 需要存储或传递不同类型回调函数的通用接口 |
std::bind | 参数绑定,生成新函数 | 需要固定函数的部分参数,或重新排列参数顺序 |
std::mem_fn | 成员函数调用的语法糖 | 在STL算法中简洁地调用成员函数,或统一用对象/指针调用 |
这三者常常结合使用,尤其是std::bind和std::mem_fn的返回值可以很方便地被std::function包裹,从而实现高度的灵活性。
// 组合使用示例:创建一个function,它调用某个对象的成员函数
MyClass obj;
// 用bind绑定成员函数和对象,生成一个无参的可调用对象
std::function<void()> callback = std::bind(&MyClass::member_func, &obj, 123, "Callback");
// 将其存入某个队列,在需要的时候调用
callback(); // 最终会调用 obj.member_func(123, "Callback")
更多推荐
所有评论(0)