在C++11标准之前,处理不同类型的可调用对象(函数指针、成员函数指针、仿函数等)时,我们需要为每种类型单独定义变量或参数,这给代码复用和泛型编程带来了诸多不便。C++11引入的<functional>头文件中的function和bind,彻底改变了这一局面。
function本质上是一个多态的函数包装器,它能够统一处理各种可调用对象。想象你有一个工具箱,function就像是一个万能工具架,可以把螺丝刀、扳手、锤子等各种工具(可调用对象)都整齐地挂上去,使用时只需要从架子上取下来就行,不用关心具体是什么工具。
bind则更像是一个功能强大的适配器,它允许我们:
这两个工具配合使用,可以极大提升代码的灵活性和可维护性。下面我们通过具体示例来深入理解它们的用法。
function的声明语法非常直观:
cpp复制#include <functional>
std::function<返回类型(参数类型列表)> 变量名;
来看一个综合示例:
cpp复制#include <iostream>
#include <functional>
using namespace std;
// 普通函数
int add(int a, int b) { return a + b; }
// lambda表达式
auto mod = [](int a, int b) { return a % b; };
// 仿函数
struct Multiply {
int operator()(int a, int b) { return a * b; }
};
int main() {
function<int(int, int)> op; // 声明function对象
op = add;
cout << "10 + 5 = " << op(10, 5) << endl; // 15
op = mod;
cout << "10 % 3 = " << op(10, 3) << endl; // 1
op = Multiply();
cout << "10 * 5 = " << op(10, 5) << endl; // 50
// 直接包装lambda
op = [](int a, int b) { return a - b; };
cout << "10 - 5 = " << op(10, 5) << endl; // 5
return 0;
}
注意:function对象在未绑定任何可调用对象时为空,调用空function会抛出std::bad_function_call异常。安全做法是在调用前检查:
cpp复制if(op) { // 或者 op != nullptr op(10, 5); }
包装成员函数时需要特别注意this指针的处理。成员函数实际上有一个隐式的this参数,因此在function的类型声明中需要显式指定:
cpp复制class Calculator {
public:
double power(double base, double exp) {
return pow(base, exp);
}
static int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n-1);
}
};
int main() {
// 静态成员函数和普通函数包装方式相同
function<int(int)> f1 = &Calculator::factorial;
cout << "5! = " << f1(5) << endl; // 120
// 非静态成员函数需要指定对象指针
function<double(Calculator*, double, double)> f2 = &Calculator::power;
Calculator calc;
cout << "2^10 = " << f2(&calc, 2, 10) << endl; // 1024
// 也可以直接绑定对象实例
function<double(double, double)> f3 = bind(&Calculator::power, &calc,
placeholders::_1, placeholders::_2);
cout << "3^4 = " << f3(3, 4) << endl; // 81
return 0;
}
在实际工程中,我建议对成员函数的包装统一使用bind方式(如f3),这样调用时就不需要每次都传递对象指针,代码更加简洁。
bind的主要功能是创建可调用对象的适配版本,其基本语法为:
cpp复制auto newCallable = bind(callable, arg_list);
其中arg_list可以包含:
来看一个参数重排序的示例:
cpp复制#include <functional>
using namespace std::placeholders; // 对于_1, _2等占位符
void printCoordinates(int x, int y, int z) {
cout << "(" << x << ", " << y << ", " << z << ")" << endl;
}
int main() {
// 原始调用
printCoordinates(1, 2, 3); // (1, 2, 3)
// 绑定z为固定值100,交换x和y
auto f = bind(printCoordinates, _2, _1, 100);
f(10, 20); // (20, 10, 100)
// 绑定x为50,y为60,z由调用提供
auto g = bind(printCoordinates, 50, 60, _1);
g(70); // (50, 60, 70)
return 0;
}
bind真正强大的地方在于它可以创建复杂的函数适配器。下面我们实现一个判断数字是否在指定范围内的函数:
cpp复制bool isInRange(int value, int low, int high) {
return value >= low && value <= high;
}
int main() {
// 创建一个检查数字是否在[10,20]范围的函数
auto isBetween10And20 = bind(isInRange, _1, 10, 20);
cout << "15 in range: " << isBetween10And20(15) << endl; // true
cout << "25 in range: " << isBetween10And20(25) << endl; // false
// 创建一个检查数字是否小于100的函数
auto isLessThan100 = bind(isInRange, _1, numeric_limits<int>::min(), 99);
cout << "50 < 100: " << isLessThan100(50) << endl; // true
cout << "100 < 100: " << isLessThan100(100) << endl; // false
return 0;
}
在实际项目中,我经常使用bind来创建回调函数的适配版本,特别是在处理异步操作时,可以预先绑定一些上下文参数。
在事件驱动编程中,function和bind是管理回调函数的利器。下面是一个简单的事件系统实现:
cpp复制#include <map>
#include <string>
#include <functional>
class EventSystem {
public:
using Callback = function<void(int)>;
void registerEvent(const string& name, Callback cb) {
callbacks[name] = cb;
}
void triggerEvent(const string& name, int value) {
if(callbacks.count(name)) {
callbacks[name](value);
}
}
private:
map<string, Callback> callbacks;
};
class Player {
public:
Player(const string& name) : name(name) {}
void onDamage(int amount) {
cout << name << " takes " << amount << " damage!" << endl;
}
private:
string name;
};
int main() {
EventSystem events;
Player player("Hero");
// 注册回调,使用bind绑定成员函数和this指针
events.registerEvent("damage", bind(&Player::onDamage, &player, _1));
// 触发事件
events.triggerEvent("damage", 50); // Hero takes 50 damage!
return 0;
}
function可以优雅地实现策略模式,避免复杂的类继承体系:
cpp复制class SortStrategy {
public:
using Comparator = function<bool(int, int)>;
void sort(vector<int>& data, Comparator comp) {
// 简化的冒泡排序用于演示
for(size_t i = 0; i < data.size(); ++i) {
for(size_t j = i+1; j < data.size(); ++j) {
if(comp(data[i], data[j])) {
swap(data[i], data[j]);
}
}
}
}
};
int main() {
vector<int> numbers = {5, 2, 8, 1, 9};
SortStrategy sorter;
// 升序排序
sorter.sort(numbers, [](int a, int b) { return a > b; });
for(int n : numbers) cout << n << " "; // 1 2 5 8 9
cout << endl;
// 降序排序
sorter.sort(numbers, [](int a, int b) { return a < b; });
for(int n : numbers) cout << n << " "; // 9 8 5 2 1
cout << endl;
// 按与5的距离排序
sorter.sort(numbers, [](int a, int b) {
return abs(a - 5) > abs(b - 5);
});
for(int n : numbers) cout << n << " "; // 5 2 8 1 9
cout << endl;
return 0;
}
虽然function和bind提供了极大的灵活性,但它们也有一些性能开销:
在性能敏感的代码中(如高频调用的循环内部),应该避免不必要的function和bind使用。但在大多数应用代码中,这种开销是可以接受的。
类型别名:对于复杂的function类型,使用using定义类型别名
cpp复制using MathOperation = function<double(double, double)>;
MathOperation op = [](double a, double b) { return a + b; };
优先使用lambda:C++14后,lambda通常比bind更清晰易读
cpp复制// 使用bind
auto f = bind(func, _1, 42, _2);
// 使用lambda(更推荐)
auto f = [](auto a, auto b) { return func(a, 42, b); };
避免过度嵌套:深度的bind嵌套会使代码难以理解和维护
注意生命周期:bind会存储绑定参数的副本或引用,要确保被绑定对象的生命周期足够长
与auto结合:当function类型复杂时,可以考虑使用auto
cpp复制auto callback = [](int a, double b) -> string { ... };
// 比下面的更简洁
function<string(int, double)> callback = [](int a, double b) { ... };
在实际项目中,我通常会先使用function和bind快速实现功能,然后在性能分析后决定是否需要优化热点代码。这种组合在实现回调系统、策略模式、命令模式等设计模式时特别有用,能够显著减少样板代码,提高代码的可读性和可维护性。