在C++11标准中,引入了std::function
这一通用多态函数包装器,定义于<functional>
头文件中。它彻底改变了C++中函数对象的使用方式,为不同类型的可调用实体提供了统一的接口。std::function
能够存储、复制和调用任何可复制构造的可调用目标,包括函数指针、lambda表达式、std::bind
表达式、函数对象以及成员函数指针等。这一特性极大地增强了C++在回调机制、事件处理和泛型编程方面的灵活性。
std::function
的核心声明如下:
template< class >
class function; /* 未定义的主模板 */
template< class R, class... Args >
class function<R(Args...)>; /* 特化版本 */
其中,R
是返回类型,Args...
是参数类型列表。这种声明方式允许std::function
包装任意签名的可调用对象。
std::function
提供了以下关键成员类型:
类型 | 定义 |
---|---|
| 返回类型 |
| 当参数数量为1时的参数类型(C++17中弃用,C++20中移除) |
| 当参数数量为2时的第一个参数类型(C++17中弃用,C++20中移除) |
| 当参数数量为2时的第二个参数类型(C++17中弃用,C++20中移除) |
std::function
的主要操作接口包括:
std::function
实例,可接受各种可调用对象std::function
实例std::function
实例的内容typeid
)std::function
的强大之处在于其能够统一处理各种可调用实体。以下是基于cppreference示例的扩展演示:
#include <functional>
#include <iostream>
void print_num(int i) {
std::cout << i << '\n';
}
int main() {
// 存储自由函数
std::function<void(int)> f_display = print_num;
f_display(-9); // 输出: -9
}
// 存储lambda表达式
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42(); // 输出: 42
// 存储std::bind的结果
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337(); // 输出: 31337
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_ + i << '\n'; }
int num_;
};
// 存储成员函数
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1); // 输出: 314160
// 存储数据成员访问器
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n'; // 输出: num_: 314159
// 结合std::bind存储成员函数(绑定对象)
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
f_add_display2(2); // 输出: 314161
// 结合std::bind存储成员函数(绑定对象指针)
std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
f_add_display3(3); // 输出: 314162
struct PrintNum {
void operator()(int i) const {
std::cout << i << '\n';
}
};
// 存储函数对象
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18); // 输出: 18
std::function
的一个高级应用是实现递归Lambda表达式:
auto factorial = [](int n) {
// 存储lambda对象以模拟"递归lambda"
std::function<int(int)> fac = [&](int n) {
return (n < 2) ? 1 : n * fac(n - 1);
};
return fac(n);
};
for (int i{5}; i != 8; ++i)
std::cout << i << "! = " << factorial(i) << "; ";
// 输出: 5! = 120; 6! = 720; 7! = 5040;
std::function
的实现基于类型擦除(Type Erasure) 技术,这是一种在C++中实现多态行为而不依赖继承的机制。其核心思想是:
std::function
存储一个指向该接口的指针,在运行时动态绑定到具体实现这种机制使得std::function
能够在编译时接受任意类型的可调用对象,而在运行时保持类型安全。类型擦除的实现通常涉及模板和多态的结合,带来一定的运行时开销(主要是虚函数调用和堆内存分配)。
std::function
在现代C++编程中有着广泛的应用:
在事件驱动编程中,std::function
可以统一管理不同类型的回调函数:
class Button {
public:
using Callback = std::function<void()>;
void set_on_click(Callback cb) {
on_click_ = std::move(cb);
}
void click() const {
if (on_click_) { // 检查是否有回调
on_click_(); // 调用回调
}
}
private:
Callback on_click_;
};
// 使用示例
Button btn;
btn.set_on_click([]() { std::cout << "Button clicked!\n"; });
btn.click(); // 触发回调
std::function
可以轻松实现函数表(Function Table),用于策略模式:
#include <unordered_map>
enum class Operation { Add, Subtract, Multiply };
int main() {
std::unordered_map<Operation, std::function<int(int, int)>> operations;
operations[Operation::Add] = [](int a, int b) { return a + b; };
operations[Operation::Subtract] = [](int a, int b) { return a - b; };
operations[Operation::Multiply] = [](int a, int b) { return a * b; };
std::cout << "3 + 4 = " << operations[Operation::Add](3, 4) << '\n';
std::cout << "5 - 2 = " << operations[Operation::Subtract](5, 2) << '\n';
std::cout << "2 * 6 = " << operations[Operation::Multiply](2, 6) << '\n';
}
在异步编程中,std::function
常用于表示异步操作完成后的回调:
// 伪代码示例
std::future<int> async_calculate(std::function<int()> func) {
return std::async(std::launch::async, func);
}
// 使用
auto future = async_calculate([]() {
// 耗时计算
return 42;
});
// 注册完成回调(实际实现可能更复杂)
使用std::function
时,需要注意以下几点:
调用空的std::function
对象会抛出std::bad_function_call
异常:
std::function<void()> f;
try {
f(); // 空函数调用
} catch (const std::bad_function_call& e) {
std::cout << "Error: " << e.what() << '\n';
}
因此,在调用前应检查std::function
是否为空:
if (f) { // 等价于 if (f.operator bool())
f();
}
在C++11中,当std::function
存储返回引用的函数时,如果实际返回的是临时对象,会导致悬垂引用:
// C++11中未定义行为,C++23中禁止
std::function<const int&()> F([] { return 42; });
int x = F(); // 未定义行为:引用绑定到临时对象
正确的做法是确保返回的引用指向有效对象:
// 正确示例
std::function<int&()> G([]() -> int& {
static int i{42};
return i;
});
std::function
的类型擦除机制带来了一定的性能开销,包括:
因此,在性能敏感的场景中,应权衡灵活性和性能,考虑是否需要使用std::function
,或是否可以使用模板代替。
std::function
与auto
在存储lambda表达式时有本质区别:
auto
根据初始化表达式推导精确类型,无运行时开销std::function
可以存储任意类型的可调用对象,但有运行时开销auto
无法用于存储不同类型的可调用对象(如函数表)auto lambda = []() { /* ... */ }; // 精确类型
std::function<void()> func = lambda; // 类型擦除,有开销
std::function
是C++11引入的强大工具,为不同类型的可调用对象提供了统一的包装接口,极大地增强了C++的表达能力。在使用时,应遵循以下最佳实践:
std::function
std::function
是否为空auto
或模板std::function
与lambda表达式、std::bind
共同构成了C++11及以后版本中函数式编程的基础,掌握这些工具能够编写更加灵活、模块化的C++代码。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。