Cpp Callable Objects
在 C++ 中,Callable Object(可调用对象) 指的是任何可以像函数一样被调用的事物。简单来说,就是任何可以使用 () 运算符(Function Call Operator)的对象。
在现代 C++(C++11 及以后)中,可调用对象的概念非常重要,因为它是 STL 算法、线程(std::thread)以及回调机制的核心。
C++ 中的可调用对象主要分为以下 5 类。
1. 普通函数与函数指针 (Function Pointers)
这是最基础的形式,继承自 C 语言。
- 特点: 没有状态(Stateless),行为固定。
- 适用场景: 简单的回调,也就是所谓的 C 风格 API。
#include <iostream>
void hello() {
std::cout << "Hello from Function Pointer!" << std::endl;
}
int main() {
// 定义函数指针
void (*funcPtr)() = &hello; // & 是可选的
// 调用
funcPtr();
return 0;
}
2. 仿函数 (Functors / Function Objects)
仿函数是 C++ 面向对象特性的体现。它是重载了 operator() 的类对象。
特点:
它可以拥有状态 (State):这是它比函数指针强大的核心原因。你可以通过成员变量保存数据。
内联优化:编译器很容易将仿函数的调用内联(Inline),通常比函数指针更快。
适用场景: 需要在多次调用之间保持状态,或者需要高度优化的 STL 算法(如
std::sort)。
#include <iostream>
class Adder {
int distinct_val; // 内部状态
public:
Adder(int v) : distinct_val(v) {}
// 重载 () 运算符
int operator()(int x) const {
return x + distinct_val;
}
};
int main() {
Adder add10(10); // 创建一个“加10”的加法器
std::cout << add10(5) << std::endl; // 输出 15
return 0;
}
3. Lambda 表达式 (Lambda Expressions)
引入于 C++11,Lambda 是现代 C++ 最常用的可调用对象。
- 本质: Lambda 实际上是匿名仿函数的语法糖。编译器在幕后为你生成了一个重载了
operator()的类。 - 特点: 代码紧凑,可以直接在调用点定义。
- 捕获列表
[]: 允许你捕获上下文中的变量(按值或按引用),这对应仿函数的成员变量。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int factor = 2;
std::vector<int> nums = {1, 2, 3};
// [factor] 是捕获列表,捕获外部变量
std::for_each(nums.begin(), nums.end(), [factor](int n) {
std::cout << n * factor << " ";
});
// 输出: 2 4 6
return 0;
}
4. std::function (通用多态包装器)
std::function 是 C++11 引入的一个标准库模板类,位于 <functional> 头文件。
- 特点: 它是一个类型擦除(Type Erasure)的容器。它可以存储任何符合特定签名的可调用对象(函数指针、仿函数、Lambda 等)。
- 代价: 由于使用了虚函数机制和可能的堆内存分配,它比直接使用 Lambda 或模板有轻微的性能开销。
- 适用场景: 当你需要存储不同类型的回调,或者作为函数参数不需要模板化时。
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
// 1. 包装普通函数
std::function<int(int, int)> func = add;
// 2. 包装 Lambda
func = [](int a, int b) { return a * b; };
// 3. 包装仿函数
struct Divisor { int operator()(int a, int b) { return a / b; } };
func = Divisor();
std::cout << func(10, 2) << std::endl; // 调用的是最后赋值的 Divisor,输出 5
return 0;
}
5. 类的成员函数指针 (Pointers to Member Functions)
这是一个比较特殊且语法晦涩的类别。非静态成员函数需要依赖一个对象实例才能调用。
- 难点: 不能直接像
f()那样调用,通常需要(obj.*ptr)(args)或(objptr->*ptr)(args)。 - 现代解法: 使用
std::mem_fn或std::bind(虽已过时),或者在 C++17 中使用std::invoke。
#include <iostream>
#include <functional>
class Foo {
public:
void print(int x) { std::cout << "Foo: " << x << std::endl; }
};
int main() {
Foo obj;
// 定义成员函数指针
void (Foo::*ptr)(int) = &Foo::print;
// 传统调用方式 (非常丑陋)
(obj.*ptr)(42);
// 现代方式:使用 std::mem_fn
auto runnable = std::mem_fn(&Foo::print);
runnable(obj, 42); // 第一个参数必须是对象实例
return 0;
}
总结与对比
为了让你更直观地理解,我做了一个对比表:
| 类型 | 是否有状态 | 灵活性 | 性能 | 典型用途 |
|---|---|---|---|---|
| 函数指针 | 无 | 低 | 高 (但在内联方面不如仿函数) | C 接口兼容,简单的全局回调 |
| 仿函数 | 有 | 中 | 极高 (易被编译器内联) | 需要状态的复杂逻辑,STL 算法 |
| Lambda | 有 (通过捕获) | 高 | 极高 (同仿函数) | 绝大多数现代 C++ 场景,局部逻辑 |
| std::function | 有 | 极高 (可存任何类型) | 中 (虚函数开销,堆分配) | API 接口设计,存储异构回调列表 |
| 成员函数指针 | 依赖对象 | 低 | 高 | 特定类操作,通常配合 bind/mem_fn 使用 |
特别提及:std::invoke (C++17)
由于上面提到的调用方式五花八门(有的直接用 (),有的要用 .*),C++17 引入了 std::invoke 来统一所有可调用对象的调用语法。
1. 统一调用语法
std::invoke 的强大之处在于它抹平了普通函数、Lambda、成员函数甚至成员变量之间的调用差异。
#include <iostream>
#include <functional> // std::invoke 所在头文件
struct MyStruct {
int value{42};
void printSum(int n) const {
std::println("Sum: {}", value + n);
}
};
void plainFunction(int n) {
std::println("Plain: {}", n);
}
int main() {
MyStruct obj;
// 1. 调用普通函数
std::invoke(plainFunction, 10);
// 2. 调用 Lambda
auto lambda = [](int n) { std::println("Lambda: {}", n); };
std::invoke(lambda, 20);
// 3. 调用成员函数 (第一个参数是对象引用或指针)
std::invoke(&MyStruct::printSum, obj, 30);
// 4. 访问成员变量 (没错,成员变量也被视为“可调用”的,返回其值)
std::println("Member value: {}", std::invoke(&MyStruct::value, obj));
return 0;
}2. 为什么需要它?(泛型编程的神器)
如果你在编写一个模板函数,需要接受一个“可调用对象”并执行它,在没有 std::invoke 之前,你很难处理成员函数指针。
坏品味的代码 (C++11 以前):
template <typename F, typename... Args>
void callIt(F f, Args&&... args) {
// 如果 f 是成员函数指针,这里会编译报错!
// 你必须写一堆 std::enable_if 或重载来区分情况
f(std::forward<Args>(args)...);
}好品味的代码 (使用 std::invoke):
template <typename F, typename... Args>
auto callIt(F&& f, Args&&... args) {
// 无论 f 是什么,一把梭,全部搞定
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}在现代 C++ 的库开发中,std::invoke 是实现高阶函数(如 std::thread 的构造函数、std::async 等)的基石。它让代码更简洁,消除了不必要的特殊情况处理。










