C++ std::function
什么是 std::function?
std::function
是 C++11 引入的一个模板类,它是一个通用的函数包装器,可以存储、复制和调用任何可调用对象(如函数、lambda表达式、绑定表达式或其他函数对象),只要这些对象的调用特征(返回类型和参数类型)符合模板参数的要求。
简单来说,std::function
是一个函数容器,可以存储普通函数、成员函数、函数指针、lambda表达式等多种可调用实体。它属于 C++ 标准库的函数对象(Functor)部分,定义在 <functional>
头文件中。
基本语法
#include <functional>
// 定义一个 std::function 对象
std::function<返回类型(参数类型列表)> 函数对象名;
std::function
的模板参数是一个函数类型,指定了函数的签名(返回类型和参数类型)。
为什么需要 std::function?
在 C++11 之前,如果我们想要实现一个接受"函数"作为参数的函数,通常会使用函数指针。但是函数指针有一些局限性:
- 不能存储带有状态的函数对象(如闭包)
- 不能直接存储成员函数
- 语法不够直观
std::function
解决了这些问题,提供了一个统一的接口来处理各种可调用对象。
std::function 的基本用法
存储普通函数
#include <iostream>
#include <functional>
// 普通函数
int add(int a, int b) {
return a + b;
}
int main() {
// 将普通函数存储到 std::function 对象中
std::function<int(int, int)> func = add;
// 调用函数
std::cout << "调用结果: " << func(5, 3) << std::endl;
return 0;
}
输出:
调用结果: 8
存储 Lambda 表达式
#include <iostream>
#include <functional>
int main() {
// 将 lambda 表达式存储到 std::function 对象中
std::function<int(int, int)> multiply = [](int a, int b) {
return a * b;
};
std::cout << "5 * 3 = " << multiply(5, 3) << std::endl;
return 0;
}
输出:
5 * 3 = 15
存储函数对象(Functor)
#include <iostream>
#include <functional>
// 自定义函数对象
struct Divider {
int operator()(int a, int b) const {
return a / b;
}
};
int main() {
// 将函数对象存储到 std::function 对象中
std::function<int(int, int)> divide = Divider();
std::cout << "10 / 2 = " << divide(10, 2) << std::endl;
return 0;
}
输出:
10 / 2 = 5
存储成员函数
#include <iostream>
#include <functional>
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
};
int main() {
Calculator calc;
// 存储成员函数(需要使用 std::bind 或 lambda)
std::function<int(int, int)> adder = [&calc](int a, int b) {
return calc.add(a, b);
};
// 或者使用 std::bind
std::function<int(int, int)> adder2 = std::bind(&Calculator::add, &calc,
std::placeholders::_1,
std::placeholders::_2);
std::cout << "3 + 4 = " << adder(3, 4) << std::endl;
std::cout << "5 + 6 = " << adder2(5, 6) << std::endl;
return 0;
}
输出:
3 + 4 = 7
5 + 6 = 11
std::function 的高级特性
函数对象的传递与存储
std::function
可以作为函数参数或返回值,这使得函数式编程在 C++ 中变得更加容易。
#include <iostream>
#include <functional>
#include <vector>
// 函数接受一个操作函数作为参数
void processNumbers(const std::vector<int>& nums,
std::function<void(int)> processor) {
for (int num : nums) {
processor(num);
}
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 打印数字
processNumbers(numbers, [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;
// 打印数字的平方
processNumbers(numbers, [](int n) {
std::cout << n * n << " ";
});
std::cout << std::endl;
return 0;
}
输出:
1 2 3 4 5
1 4 9 16 25
默认构造与空函数检查
默认构造的 std::function
不包含任何可调用对象,称为空函数对象。我们可以通过 operator bool()
检查函数对象是否为空。
#include <iostream>
#include <functional>
void greet() {
std::cout << "Hello, World!" << std::endl;
}
int main() {
// 创建空函数对象
std::function<void()> empty;
// 创建非空函数对象
std::function<void()> func = greet;
// 检查函数对象是否为空
if (empty) {
std::cout << "empty 不是空函数对象" << std::endl;
} else {
std::cout << "empty 是空函数对象" << std::endl;
}
if (func) {
std::cout << "func 不是空函数对象" << std::endl;
func(); // 调用函数
}
return 0;
}
输出:
empty 是空函数对象
func 不是空函数对象
Hello, World!
调用空的 std::function 对象会抛出 std::bad_function_call 异常!因此使用前应该先检查函数对象是否为空。
实际应用场景
回调函数
std::function
最常见的应用场景之一是实现回调机制,特别是在事件驱动的编程中。
#include <iostream>
#include <functional>
#include <string>
class Button {
private:
std::string m_name;
std::function<void()> m_onClick;
public:
Button(const std::string& name) : m_name(name), m_onClick(nullptr) {}
// 设置点击回调
void setOnClickHandler(std::function<void()> handler) {
m_onClick = handler;
}
// 模拟用户点击
void click() {
std::cout << "按钮 '" << m_name << "' 被点击!" << std::endl;
if (m_onClick) {
m_onClick();
}
}
};
int main() {
Button submitButton("提交");
Button cancelButton("取消");
// 设置点击回调
submitButton.setOnClickHandler([]() {
std::cout << "正在提交表单..." << std::endl;
});
cancelButton.setOnClickHandler([]() {
std::cout << "已取消操作!" << std::endl;
});
// 模拟用户点击
submitButton.click();
cancelButton.click();
return 0;
}
输出:
按钮 '提交' 被点击!
正在提交表单...
按钮 '取消' 被点击!
已取消操作!
策略模式
std::function
可以用来实现策略设计模式,使算法在运行时可以动态切换。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
// 使用 std::function 实现排序策略
class Sorter {
private:
std::function<bool(int, int)> m_compareStrategy;
public:
Sorter(std::function<bool(int, int)> strategy) : m_compareStrategy(strategy) {}
void sort(std::vector<int>& data) {
std::sort(data.begin(), data.end(), m_compareStrategy);
}
void setStrategy(std::function<bool(int, int)> strategy) {
m_compareStrategy = strategy;
}
};
// 打印向量的辅助函数
void printVector(const std::vector<int>& vec) {
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
std::cout << "原始数组: ";
printVector(numbers);
// 创建一个使用升序策略的排序器
Sorter sorter([](int a, int b) { return a < b; });
// 升序排序
sorter.sort(numbers);
std::cout << "升序排序: ";
printVector(numbers);
// 切换到降序策略
sorter.setStrategy([](int a, int b) { return a > b; });
// 降序排序
sorter.sort(numbers);
std::cout << "降序排序: ";
printVector(numbers);
return 0;
}
输出:
原始数组: 5 2 8 1 9 3
升序排序: 1 2 3 5 8 9
降序排序: 9 8 5 3 2 1
std::function 性能考虑
std::function
是一个强大的工具,但它确实会带来一些性能开销。与直接函数调用相比,std::function
可能会引入额外的间接调用和可能的堆分配。
当性能至关重要时,可以考虑以下几点:
- 对于简单的场景,可以使用函数指针而不是
std::function
- 如果可能,使用模板而不是
std::function
- 注意
std::function
对象的复制开销
然而,对于大多数应用程序来说,std::function
带来的灵活性和易用性通常超过了性能开销的影响。
std::function vs 函数指针
下表比较了 std::function
和传统函数指针的区别:
特性 | std::function | 函数指针 |
---|---|---|
语法清晰度 | 高 | 低 |
存储 Lambda | 支持 | 不支持(C++23前) |
存储函数对象 | 支持 | 不支持 |
存储成员函数 | 支持(需绑定) | 支持特定语法 |
性能开销 | 较高 | 很低 |
类型擦除 | 支持 | 不支持 |
总结
std::function
是 C++11 引入的强大功能,为函数式编程提供了极大便利:
- 它可以存储、复制和调用多种可调用对象(普通函数、lambda、函数对象、成员函数)
- 它提供了类型安全的函数封装,使参数传递更加灵活
- 它在回调机制、策略模式等设计模式中有广泛应用
- 相比函数指针,它提供了更强的表达能力和更高的灵活性
然而,它也带来一些性能开销,在性能敏感的场景需要谨慎使用。
练习
- 使用
std::function
创建一个简单的计算器,支持加、减、乘、除四种操作。 - 实现一个简单的事件系统,允许注册和触发多个回调函数。
- 尝试使用
std::function
实现一个命令模式,允许用户执行和撤销操作。 - 比较直接调用函数与通过
std::function
调用函数的性能差异。
附加资源
- C++ Reference: std::function
- C++11 函数式编程
- Effective Modern C++ by Scott Meyers