std::functional 使用场景

std::functional 是 C++ 标准库中一个非常强大的工具,它提供了一种**类型擦除(type erasure)**机制,让你能够存储、传递和调用任何可调用对象(callable)。

核心作用

1. 统一的可调用对象包装器

std::function 可以包装任何可调用实体,只要签名匹配:

  • 普通函数
  • Lambda 表达式
  • 函数对象(仿函数)
  • 成员函数(通过 std::bind 或 lambda)
  • 甚至其他 std::function

2. 类型擦除

隐藏具体类型,只暴露接口。这使得你可以:

  • 在容器中存储不同类型的可调用对象
  • 作为函数参数接受任意可调用对象
  • 实现回调机制而不需要模板

主要使用场景

场景 1:回调函数(Callbacks)

cpp 复制代码
#include <functional>
#include <iostream>

class Button {
    std::function<void()> onClick_;
public:
    void setOnClick(std::function<void()> callback) {
        onClick_ = callback;
    }
    void click() { if (onClick_) onClick_(); }
};

// 使用
Button btn;
btn.setOnClick([]() { std::cout << "Clicked!\n"; });
btn.click();

场景 2:策略模式 / 算法注入

cpp 复制代码
#include <functional>
#include <vector>
#include <algorithm>

void processData(std::vector<int>& data, 
                 std::function<bool(int)> filter,
                 std::function<int(int)> transform) {
    // 先过滤
    data.erase(std::remove_if(data.begin(), data.end(), 
              [&](int x) { return !filter(x); }), data.end());
    // 再转换
    for (auto& x : data) x = transform(x);
}

// 使用
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
processData(nums,
    [](int x) { return x % 2 == 0; },  // 只保留偶数
    [](int x) { return x * x; }         // 平方
);

场景 3:事件系统 / 观察者模式

cpp 复制代码
#include <functional>
#include <vector>
#include <string>

class EventSystem {
    std::vector<std::function<void(const std::string&)>> listeners_;
public:
    void subscribe(std::function<void(const std::string&)> listener) {
        listeners_.push_back(listener);
    }
    void emit(const std::string& event) {
        for (auto& listener : listeners_) {
            listener(event);
        }
    }
};

// 使用
EventSystem events;
events.subscribe([](const std::string& e) { 
    std::cout << "Logger: " << e << "\n"; 
});
events.subscribe([](const std::string& e) { 
    std::cout << "Metrics: recorded " << e << "\n"; 
});

场景 4:延迟执行 / 任务队列

cpp 复制代码
#include <functional>
#include <queue>
#include <iostream>

class TaskQueue {
    std::queue<std::function<void()>> tasks_;
public:
    void addTask(std::function<void()> task) {
        tasks_.push(task);
    }
    void runAll() {
        while (!tasks_.empty()) {
            tasks_.front()();
            tasks_.pop();
        }
    }
};

// 使用
TaskQueue queue;
queue.addTask([]() { std::cout << "Task 1\n"; });
queue.addTask([]() { std::cout << "Task 2\n"; });
queue.runAll();

场景 5:绑定成员函数

cpp 复制代码
#include <functional>
#include <iostream>

class Calculator {
public:
    int add(int a, int b) { return a + b; }
    int multiply(int a, int b) { return a * b; }
};

// 使用 std::bind
Calculator calc;
auto addFunc = std::bind(&Calculator::add, &calc, 
                         std::placeholders::_1, std::placeholders::_2);
std::cout << addFunc(3, 4); // 7

// 或者使用 lambda(更推荐,性能更好)
auto multiplyFunc = [&calc](int a, int b) { 
    return calc.multiply(a, b); 
};

std::function vs 模板 vs 裸函数指针

特性 std::function 模板 裸函数指针
类型擦除 ✅ 是 ❌ 否 ✅ 是(但只能指向函数)
存储 Lambda ✅ 可以 ✅ 可以 ❌ 不行(除非无捕获)
运行时开销 有(虚函数调用) 无 无
编译时类型检查 弱(运行时可能抛 bad_function_call) 强 强
存储在容器中 ✅ 容易 ❌ 难(需要类型擦除) ✅ 可以

最佳实践

  1. 优先使用模板:如果不需要存储可调用对象,用模板参数更高效

    cpp 复制代码
    // 更好:零开销
    template<typename Func>
    void execute(Func&& f) { f(); }
    
    // 有开销:类型擦除
    void execute(std::function<void()> f) { f(); }
  2. 检查空状态 :调用前确保 std::function 不为空

    cpp 复制代码
    if (func) func();  // 或 if (func != nullptr)
  3. 注意开销 :std::function 通常使用小对象优化(SOO),但大对象会堆分配

  4. C++23 替代 :考虑使用 std::move_only_function(仅移动)或 std::copyable_function

std::functional 的核心价值在于灵活性------当你需要在运行时决定调用什么、或者需要在容器中存储可调用对象时,它是不可或缺的工具。

相关推荐
励志不掉头发的内向程序员1 小时前
【从零写一个CAD 03】三个 double 值得单独一个类吗:把视图变换抽成 View
开发语言·c++·qt·学习·系统架构
蒸蒸yyyyzwd8 小时前
秋招学习笔记 day46
c++·八股
码匠许师傅12 小时前
【C++三方组件】cpp-httplib:一个头文件起 HTTP 服务
开发语言·c++·http
无名猿13 小时前
new/delete 与 malloc/free:为什么绝对不能混用
c++·内存管理·现代c++·踩坑记录
2601_9622186114 小时前
C++中decltype关键字的实现
开发语言·c++
繁华的地方不一定留下你的脚印15 小时前
C++ std::variant 与 std::visit:安全保存多种类型,写清每个处理分支
开发语言·c++
程序员老陆15 小时前
WIN32_LEAN_AND_MEAN:Windows 头文件里的“精简编译开关”
c++·windows
hold?fish:palm16 小时前
45 二叉树的右视图
开发语言·c++·算法
ShineWinsu16 小时前
对于Redis:Redis的认识以及分布式系统的解析
linux·数据库·c++·redis·缓存·消息队列·分布式架构
布莱克60516 小时前
在C++中,构造函数能不能是虚函数?析构函数为什么要虚?
开发语言·c++·构造函数·析构函数·虚函数