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

相关推荐
_wyt0014 小时前
洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
c++·gesp
玖玥拾8 小时前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
один but you9 小时前
constexpr函数
c++
凡人叶枫10 小时前
Effective C++ 条款41:了解隐式接口和编译期多态
java·开发语言·c++·effective c++
凡人叶枫10 小时前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
小胖xiaopangss10 小时前
BRpc使用
c++·rpc
-森屿安年-10 小时前
63. 不同路径 II
c++·算法·动态规划
chase_my_dream10 小时前
Cartographer详细讲解
c++·人工智能·自动驾驶
森G10 小时前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt
碧海蓝天202211 小时前
C++法则24:在标准 C++ 中,没有任何可移植的方式判断指针 T* pt 指向的内存位置是否已经 构造了对象,程序员必须手动跟踪哪些元素已构造。
java·开发语言·c++