乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

函数对象与 std::function

定义与作用

函数对象(Function Object / Functor)是重载了 operator() 的类的实例,可以像函数一样被"调用"。与普通函数相比,函数对象可以携带状态:

class Adder {
    int base_;
public:
    Adder(int base) : base_(base) {}
    int operator()(int x) const { return base_ + x; }
};

Adder add5(5);
add5(10);  // 返回 15——带状态的计算

std::function(C++11)是类型擦除的通用可调用对象包装器,可以统一存储:普通函数、Lambda、函数对象、成员函数指针、std::bind 结果等。

机制特点何时使用
函数对象(Functor)编译期确定类型,零开销状态简单的局部回调
Lambda匿名 Functor,就地定义短小的一次性回调
std::function类型擦除,有运行时开销需要存储/传递不同类型可调用对象
std::bind参数绑定适配函数签名(C++14 起推荐 Lambda 替代)

核心原理

std::function 的类型擦除机制

可调用对象体系

完整示例

示例一:函数对象实现灵活的比较策略

场景说明:学生成绩系统使用函数对象封装可配置的比较策略。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

struct Student {
    std::string name;
    int id;
    double gpa;
};

// 按 GPA 比较(降序)
struct CompareByGPA {
    explicit CompareByGPA(bool ascending = false)
        : ascending_(ascending) {}

    bool operator()(const Student& a, const Student& b) const {
        return ascending_ ? a.gpa < b.gpa : a.gpa > b.gpa;
    }

private:
    bool ascending_;  // 状态:升序还是降序
};

// 按 ID 比较
struct CompareByID {
    bool operator()(const Student& a, const Student& b) const {
        return a.id < b.id;
    }
};

// 通用排序函数:通过模板参数接收任何可调用对象
template <typename Comparator>
void sort_students(std::vector<Student>& students, Comparator cmp) {
    std::sort(students.begin(), students.end(), cmp);
}

// 使用 std::function 的版本:运行时选择比较器
void sort_students_runtime(std::vector<Student>& students,
                           std::function<bool(const Student&, const Student&)> cmp) {
    std::sort(students.begin(), students.end(), cmp);
}

int main() {
    std::vector<Student> students = {
        {"张三", 1003, 3.8},
        {"李四", 1001, 3.5},
        {"王五", 1002, 4.0}
    };

    // ---- 模板版本:编译期多态,零开销 ----
    std::cout << "===== 按 GPA 降序(函数对象) =====" << std::endl;
    sort_students(students, CompareByGPA(false));
    for (const auto& s : students) {
        std::cout << s.name << " | GPA " << s.gpa << std::endl;
    }

    std::cout << "\n===== 按 GPA 升序 =====" << std::endl;
    sort_students(students, CompareByGPA(true));
    for (const auto& s : students) {
        std::cout << s.name << " | GPA " << s.gpa << std::endl;
    }

    // ---- std::function 版本:运行期多态 ----
    std::cout << "\n===== std::function 统一接口 =====" << std::endl;

    std::function<bool(const Student&, const Student&)> comparator;

    // 可以存 Lambda
    comparator = [](const Student& a, const Student& b) {
        return a.id < b.id;
    };
    sort_students_runtime(students, comparator);
    std::cout << "按 ID: ";
    for (const auto& s : students) std::cout << s.id << " ";
    std::cout << std::endl;

    // 可以存函数对象
    comparator = CompareByGPA(true);
    sort_students_runtime(students, comparator);
    std::cout << "按 GPA 升序: ";
    for (const auto& s : students) std::cout << s.name << " ";
    std::cout << std::endl;

    // 查询 std::function 是否有效
    std::function<void()> empty_func;
    if (!empty_func) {
        std::cout << "\nstd::function 可以为空(类似空指针)" << std::endl;
    }

    return 0;
}

预期输出:

===== 按 GPA 降序(函数对象) =====
王五 | GPA 4
张三 | GPA 3.8
李四 | GPA 3.5

===== 按 GPA 升序 =====
李四 | GPA 3.5
张三 | GPA 3.8
王五 | GPA 4

===== std::function 统一接口 =====
按 ID: 1001 1002 1003 
按 GPA 升序: 李四 张三 王五 

std::function 可以为空(类似空指针)

逐段分析:

  • CompareByGPA 是典型的带状态函数对象——构造时决定升降序,operator() 中使用该状态
  • CompareByID 是无状态函数对象——所有实例行为相同
  • 模板版本 sort_students 是编译期多态:每个 Comparator 类型生成单独代码,零运行时开销
  • std::function 版本是运行期多态:同一变量可换绑不同可调用对象,但有间接调用开销
  • std::function 可以像空指针一样处于"无效"状态,if (func) 判断是否有效

示例二:std::bind 与回调注册系统

场景说明:事件系统用 std::bind 适配函数签名,注册各种格式的回调。

#include <iostream>
#include <functional>
#include <vector>
#include <string>

class EventManager {
public:
    using Callback = std::function<void()>;

    void on(const std::string& event, Callback cb) {
        callbacks_[event].push_back(std::move(cb));
    }

    void trigger(const std::string& event) {
        auto it = callbacks_.find(event);
        if (it != callbacks_.end()) {
            for (auto& cb : it->second) {
                cb();
            }
        }
    }

private:
    std::map<std::string, std::vector<Callback>> callbacks_;
};

// 不同签名的处理函数
void free_handler() {
    std::cout << "  [普通函数] 事件已处理" << std::endl;
}

void param_handler(int id, const std::string& msg) {
    std::cout << "  [参数函数] ID=" << id << ", MSG=" << msg << std::endl;
}

class SystemMonitor {
public:
    void on_startup(int code) {
        std::cout << "  [成员函数] 启动码: " << code << std::endl;
    }
};

int main() {
    EventManager em;
    SystemMonitor monitor;

    // 注册方式 1:Lambda(最灵活)
    em.on("startup", []() {
        std::cout << "  [Lambda] 系统初始化..." << std::endl;
    });

    // 注册方式 2:普通函数
    em.on("startup", free_handler);

    // 注册方式 3:用 std::bind 适配参数函数
    // param_handler 有参数,但 Callback 签名是 void()
    em.on("startup", std::bind(param_handler, 100, "数据库就绪"));
    em.on("startup", std::bind(param_handler, 200, "网络就绪"));

    // 注册方式 4:用 std::bind 绑定成员函数
    // monitor.on_startup(int) → void()
    em.on("startup", std::bind(&SystemMonitor::on_startup, &monitor, 0));

    // Lambda 替代 bind(更清晰的写法)
    em.on("startup", [&monitor]() {
        monitor.on_startup(999);
    });

    std::cout << "===== 触发 startup 事件 =====" << std::endl;
    em.trigger("startup");

    return 0;
}

预期输出:

===== 触发 startup 事件 =====
  [Lambda] 系统初始化...
  [普通函数] 事件已处理
  [参数函数] ID=100, MSG=数据库就绪
  [参数函数] ID=200, MSG=网络就绪
  [成员函数] 启动码: 0
  [成员函数] 启动码: 999

逐段分析:

  • EventManager::Callback 是 std::function<void()>——统一的无参回调接口
  • std::bind(param_handler, 100, "数据库就绪") 将两参函数适配为无参可调用对象
  • std::bind(&SystemMonitor::on_startup, &monitor, 0) 绑定成员函数——需要传入对象指针
  • Lambda 写法 [&monitor]() { monitor.on_startup(999); } 语义比 std::bind 更清晰——C++14 起推荐使用 Lambda 替代 std::bind
  • 每次注册不同类型的可调用对象,std::function 通过类型擦除将它们统一存储

易错场景与面试考点

易错场景

场景错误表现正确做法
std::bind 参数按值传递大对象被拷贝用 std::ref 或 std::cref 包装引用
std::bind 中占位符顺序混乱参数位置错误仔细核对 _1、_2 的顺序
std::function 为空时调用std::bad_function_call 异常调用前检查 if (func)
std::function 频繁拷贝性能开销使用模板 + auto 替代
用 std::bind 绑定重载函数歧义,编译失败用 static_cast 明确签名或 Lambda

常见面试问题

  1. 什么是函数对象(Functor)?与普通函数的区别?——重载 operator() 的类。区别在于函数对象可以携带状态(成员变量),且是类型而非函数指针。

  2. std::function 的实现原理?它为什么有开销?——类型擦除:通过虚函数表或函数指针包装任意可调用对象。开销来自间接调用和可能的堆分配(小对象优化 SBO 可缓解)。

  3. std::bind 和 Lambda 哪个更好?为什么?——C++14 起推荐 Lambda。Lambda 语法更清晰,编译器优化更容易(可见的调用),std::bind 的参数求值时机和传参方式容易造成困惑。

  4. 如何用 std::function 存储成员函数?——std::function<void(MyClass*, int)> f = &MyClass::method; 调用时 f(&obj, 42)。或者用 std::bind 预先绑定对象。

  5. std::function 能否存储有捕获的 Lambda?——可以。std::function 通过类型擦除支持任意可调用对象,包括有捕获 Lambda。

小结

  • 函数对象 = 重载 operator() 的类,可携带状态,模板配合零开销
  • std::function = 类型擦除的统一可调用对象包装器,灵活但有运行时开销
  • std::bind 用于适配函数签名,但 C++14 起优先用 Lambda 替代
  • 性能敏感路径用模板(编译期多态),灵活性需求用 std::function(运行期多态)
  • std::function 可处于空状态,调用前检查
上一页
Lambda 表达式
下一页
后置返回类型与 noexcept