在 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_fnstd::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 等)的基石。它让代码更简洁,消除了不必要的特殊情况处理。