跳转至

std::function vs 函数指针

引言

你在写一个事件处理系统,需要存储回调函数。传统C方式是用函数指针,但现代C++又给你一个std::function。哪个更合适?在资源受限的嵌入式环境下,这可不是"随便选一个"的问题。

函数指针是C时代的遗产,轻量、直接。std::function是C++11引入的通用函数包装器,能存储任何可调用对象——函数指针、Lambda、仿函数、std::bind的结果。

一句话总结:函数指针零开销但功能受限,std::function功能强大但有运行时成本。嵌入式开发需要根据场景权衡。


函数指针:轻量级的选择

函数指针是C语言传承下来的机制,直接指向代码地址,简单而高效。

基本用法

// 普通函数
int add(int a, int b) {
    return a + b;
}

// 函数指针声明
int (*func_ptr)(int, int) = add;

// 使用
int result = func_ptr(3, 4);  // result = 7

typedefusing简化:

// C风格typedef
typedef int (*BinaryOp)(int, int);
BinaryOp op = add;

// C++11 using
using BinaryOp = int(*)(int, int);
BinaryOp op = add;
查看完整可编译示例
// Function Pointer Basics
// Demonstrates function pointer usage in embedded C++

#include <iostream>
#include <cstdint>

// Basic functions
int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

// Function pointer declaration styles
void demo_declaration_styles() {
    std::cout << "=== Declaration Styles ===" << std::endl;

    // C-style typedef
    typedef int (*BinaryOpOld)(int, int);
    BinaryOpOld old_style = add;

    // C++11 using
    using BinaryOp = int(*)(int, int);
    BinaryOp new_style = multiply;

    // Direct variable
    int (*direct)(int, int) = add;

    std::cout << "old_style(3, 4) = " << old_style(3, 4) << std::endl;
    std::cout << "new_style(3, 4) = " << new_style(3, 4) << std::endl;
    std::cout << "direct(3, 4) = " << direct(3, 4) << std::endl;
}

// Array of function pointers (state machine style)
enum class State { IDLE, RUNNING, ERROR, SHUTDOWN };

void handle_idle() {
    std::cout << "State: IDLE - waiting for start" << std::endl;
}

void handle_running() {
    std::cout << "State: RUNNING - processing" << std::endl;
}

void handle_error() {
    std::cout << "State: ERROR - recovering" << std::endl;
}

void handle_shutdown() {
    std::cout << "State: SHUTDOWN - cleaning up" << std::endl;
}

void demo_state_table() {
    std::cout << "\n=== State Table ===" << std::endl;

    // State handler table
    struct StateHandler {
        State state;
        void (*handler)(void);
    };

    StateHandler state_table[] = {
        {State::IDLE, handle_idle},
        {State::RUNNING, handle_running},
        {State::ERROR, handle_error},
        {State::SHUTDOWN, handle_shutdown}
    };

    // Run state machine
    for (const auto& entry : state_table) {
        entry.handler();
    }
}

// ADC callback example
using ADCCallback = void(*)(int16_t value);

volatile uint32_t* const ADC_DR = reinterpret_cast<volatile uint32_t*>(0x4001204C);

ADCCallback g_adc_callback = nullptr;

void register_adc_callback(ADCCallback callback) {
    g_adc_callback = callback;
    std::cout << "ADC callback registered" << std::endl;
}

void my_adc_handler(int16_t value) {
    if (value > 4095) {
        std::cout << "ADC overflow: " << value << std::endl;
    } else {
        std::cout << "ADC value: " << value << std::endl;
    }
}

void demo_adc_callback() {
    std::cout << "\n=== ADC Callback ===" << std::endl;

    register_adc_callback(my_adc_handler);

    // Simulate ADC interrupt
    if (g_adc_callback) {
        g_adc_callback(2048);
    }
}

// Comparison function for sorting
int compare_asc(int a, int b) {
    return a - b;  // negative if a < b
}

int compare_desc(int a, int b) {
    return b - a;  // positive if a < b
}

void demo_sort_callback() {
    std::cout << "\n=== Sort Callback ===" << std::endl;

    int data[] = {5, 2, 8, 1, 9};

    // Using function pointer for custom sort
    int (*compare)(int, int) = compare_asc;
    std::cout << "Using compare_asc" << std::endl;

    // Simple bubble sort with callback
    for (size_t i = 0; i < 5; ++i) {
        for (size_t j = 0; j < 4 - i; ++j) {
            if (compare(data[j], data[j + 1]) > 0) {
                std::swap(data[j], data[j + 1]);
            }
        }
    }

    std::cout << "Sorted: ";
    for (int v : data) {
        std::cout << v << " ";
    }
    std::cout << std::endl;
}

// Function pointer as member
class Calculator {
public:
    using Operation = int(*)(int, int);

    int compute(int a, int b, Operation op) {
        if (op) {
            return op(a, b);
        }
        return 0;
    }
};

void demo_member_function_pointer() {
    std::cout << "\n=== Member with Function Pointer ===" << std::endl;

    Calculator calc;
    std::cout << "calc.compute(5, 3, add) = " << calc.compute(5, 3, add) << std::endl;
    std::cout << "calc.compute(5, 3, multiply) = " << calc.compute(5, 3, multiply) << std::endl;
}

// Size of function pointer
void demo_size() {
    std::cout << "\n=== Size Comparison ===" << std::endl;
    std::cout << "sizeof(int(*)(int, int)) = " << sizeof(int(*)(int, int)) << std::endl;
    std::cout << "sizeof(void(*)(void)) = " << sizeof(void(*)(void)) << std::endl;
    std::cout << "Function pointer is just an address - very lightweight!" << std::endl;
}

int main() {
    std::cout << "=== Function Pointer Demo ===" << std::endl;

    demo_declaration_styles();
    demo_state_table();
    demo_adc_callback();
    demo_sort_callback();
    demo_member_function_pointer();
    demo_size();

    return 0;
}

嵌入式场景示例

// ADC转换完成回调
using ADCCallback = void(*)(int16_t value);

volatile uint32_t* const ADC_DR = reinterpret_cast<volatile uint32_t*>(0x4001204C);

void register_adc_callback(ADCCallback callback);

void my_adc_handler(int16_t value) {
    if (value > 4095) {
        // 处理溢出
    }
    // 处理正常值
}

void setup_adc() {
    register_adc_callback(my_adc_handler);
    *ADC_DR |= (1 << 0);  // 启动ADC
}

函数指针的优势

特性 说明
零开销 本质就是一个指针,sizeof通常等于指针大小
可预测 编译期确定,没有动态分配
直接调用 生成的汇编代码就是简单的call指令
ROM友好 可以放进ROM,适用于表驱动法
// 表驱动法:函数指针数组非常适合嵌入式
struct StateHandler {
    int state;
    void (*handler)(void);
};

StateHandler state_table[] = {
    {0, handle_idle},
    {1, handle_running},
    {2, handle_error},
    {3, handle_shutdown}
};

void run_state_machine(int current_state) {
    for (const auto& entry : state_table) {
        if (entry.state == current_state) {
            entry.handler();  // 直接调用,零开销
            break;
        }
    }
}

函数指针的局限

// ❌ 函数指针无法捕获上下文
class SensorManager {
    int sensor_id;

    // 函数指针不能存储this指针
    void setup() {
        // 错误:无法让成员函数指针携带this
        register_callback([](int v) {  // Lambda不是函数指针!
            this->process(v);
        });
    }
};

函数指针只能指向静态函数全局函数,无法携带额外的上下文信息。这是它在面向对象设计中的致命缺陷。


std::function:通用函数包装器

std::function是C++11引入的类型擦除容器,可以存储、复制和调用任何可调用对象。

基本用法

#include <functional>

std::function<int(int, int)> func;

// 存储普通函数
func = add;
int r1 = func(3, 4);

// 存储Lambda
func = [](int a, int b) { return a * b; };
int r2 = func(3, 4);

// 存储仿函数
struct Multiplier {
    int factor;
    int operator()(int x) const { return x * factor; }
};
func = Multiplier{5};
int r3 = func(10);  // 返回50

存储捕获上下文的Lambda

这是std::function相对于函数指针的核心优势:

class Button {
    int pin;
    int debounce_count = 0;

public:
    // 可以存储带捕获的Lambda
    std::function<void()> get_handler() {
        return [this]() {
            debounce_count++;
            // 访问成员变量
            if (*GPIO_IN & (1 << pin)) {
                // 处理按键
            }
        };
    }
};
查看完整可编译示例
// std::function Demo
// Demonstrates std::function usage and its capabilities

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

// Basic functions
int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

// Functor
struct Multiplier {
    int factor;
    Multiplier(int f) : factor(f) {}
    int operator()(int x) const { return x * factor; }
};

void demo_basics() {
    std::cout << "=== std::function Basics ===" << std::endl;

    std::function<int(int, int)> func;

    // Store a function pointer
    func = add;
    std::cout << "func(3, 4) = " << func(3, 4) << std::endl;

    // Store a lambda
    func = [](int a, int b) { return a * b; };
    std::cout << "func(3, 4) = " << func(3, 4) << std::endl;

    // Store a functor
    func = Multiplier(5);
    std::cout << "func(10) with Multiplier(5) = " << func(10, 0) << std::endl;
}

// Capturing context - the key advantage
class Button {
    int pin;
    int debounce_count = 0;

public:
    Button(int p) : pin(p) {}

    std::function<void()> get_handler() {
        return [this]() {
            debounce_count++;
            std::cout << "Button on pin " << pin
                      << " pressed, count: " << debounce_count << std::endl;
        };
    }
};

void demo_context_capture() {
    std::cout << "\n=== Context Capture ===" << std::endl;

    Button btn(5);
    auto handler = btn.get_handler();
    handler();
    handler();
}

// Event loop using std::function
class EventLoop {
public:
    using Callback = std::function<void()>;

    void register_timer(int id, uint32_t interval_ms, Callback cb) {
        timers.push_back({id, interval_ms, 0, std::move(cb)});
    }

    void tick(uint32_t delta_ms) {
        for (auto& timer : timers) {
            timer.elapsed += delta_ms;
            if (timer.elapsed >= timer.interval && timer.callback) {
                timer.callback();
                timer.elapsed = 0;
            }
        }
    }

private:
    struct Timer {
        int id;
        uint32_t interval;
        uint32_t elapsed;
        Callback callback;
    };
    std::vector<Timer> timers;
};

void demo_event_loop() {
    std::cout << "\n=== Event Loop ===" << std::endl;

    EventLoop loop;
    int counter = 0;

    // Capture counter by reference
    loop.register_timer(0, 1000, [&counter]() {
        counter++;
        std::cout << "Timer fired! Counter: " << counter << std::endl;
    });

    // Simulate 3 ticks
    for (int i = 0; i < 3; ++i) {
        loop.tick(1000);
    }
}

// Storing different callable types
void demo_polymorphism() {
    std::cout << "\n=== Type Erasure ===" << std::endl;

    std::vector<std::function<void()>> tasks;

    // Add different types of callables
    tasks.push_back([]() {
        std::cout << "Lambda task" << std::endl;
    });

    tasks.push_back(add);
    tasks.push_back(Multiplier(3));

    for (auto& task : tasks) {
        task();
    }
}

// std::function with return types
void demo_return_types() {
    std::cout << "\n=== Return Types ===" << std::endl;

    std::function<int(int)> factorial = [&](int n) -> int {
        return n <= 1 ? 1 : n * factorial(n - 1);
    };

    std::cout << "factorial(5) = " << factorial(5) << std::endl;

    // Recursive lambda with std::function
    std::function<int(int)> fib = [&](int n) -> int {
        return n <= 1 ? n : fib(n - 1) + fib(n - 2);
    };

    std::cout << "fib(10) = " << fib(10) << std::endl;
}

// Memory overhead discussion
void demo_memory_overhead() {
    std::cout << "\n=== Memory Overhead ===" << std::endl;

    std::cout << "sizeof(std::function<void()>) = "
              << sizeof(std::function<void()>) << " bytes" << std::endl;
    std::cout << "sizeof(void(*)()) = "
              << sizeof(void(*)()) << " bytes" << std::endl;

    // Small Object Optimization (SOO)
    std::function<void()> small = [x = 42]() { std::cout << x << std::endl; };
    std::cout << "Small lambda in std::function: no heap allocation" << std::endl;

    // Large lambda may cause heap allocation
    std::function<void()> large = [big = std::array<int, 1000>()]() {
        std::cout << "Large lambda" << std::endl;
    };
    std::cout << "Large lambda: may use heap allocation" << std::endl;
}

// Checking if std::function is empty
void demo_empty_check() {
    std::cout << "\n=== Empty Check ===" << std::endl;

    std::function<void()> func;

    if (!func) {
        std::cout << "Function is empty" << std::endl;
    }

    func = []() { std::cout << "Now it has a target" << std::endl; };

    if (func) {
        std::cout << "Function has a target" << std::endl;
        func();
    }

    func = nullptr;

    if (!func) {
        std::cout << "Cleared back to empty" << std::endl;
    }
}

int main() {
    std::cout << "=== std::function Demo ===" << std::endl;

    demo_basics();
    demo_context_capture();
    demo_event_loop();
    demo_polymorphism();
    demo_return_types();
    demo_memory_overhead();
    demo_empty_check();

    return 0;
}

嵌入式场景示例

#include <functional>
#include <array>

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

    void register_timer(int id, uint32_t interval_ms, Callback cb) {
        timers[id] = {interval_ms, 0, std::move(cb)};
    }

    void tick(uint32_t delta_ms) {
        for (auto& timer : timers) {
            timer.elapsed += delta_ms;
            if (timer.elapsed >= timer.interval && timer.callback) {
                timer.callback();
                timer.elapsed = 0;
            }
        }
    }

private:
    struct Timer {
        uint32_t interval;
        uint32_t elapsed;
        Callback callback;
    };
    std::array<Timer, 8> timers;  // 最多8个定时器
};

// 使用:可以轻松捕获上下文
void setup_application() {
    EventLoop loop;
    int led_counter = 0;

    // 捕获led_counter
    loop.register_timer(0, 1000, [&led_counter]() {
        led_counter = (led_counter + 1) % 10;
        update_display(led_counter);
    });

    // 捕获this指针
    class Sensor {
    public:
        void start(EventLoop& loop) {
            loop.register_timer(1, 100, [this]() {
                this->read();
            });
        }
    };
}

std::function的代价

特性 代价
内存占用 通常2-3个指针大小(存储函数指针+管理器+可能的分配器)
动态分配 存储大型可调用对象时可能堆分配
间接调用 需要通过虚函数表式的机制调用
代码膨胀 每个不同的签名实例化模板,增加代码体积
// 典型实现的大小(具体取决于编译器)
sizeof(std::function<void()>)      // 32位:16~32字节
                                   // 64位:32~64字节

sizeof(void(*)())                  // 32位:4字节
                                   // 64位:8字节

小对象优化(SOO):大多数实现对小可调用对象(如捕获少量变量的Lambda)避免堆分配,但这仍然比函数指针大得多。

// 小Lambda:可能不需要堆分配,但对象仍较大
auto small = [x = 42]() { return x * 2; };
std::function<int()> f = small;  // f的大小约32字节

// 大Lambda:几乎肯定需要堆分配
auto large = [big_array = std::array<int, 1000>()](){ /* ... */ };
std::function<void()> g = large;  // g+堆分配

性能对比:从汇编层面看

让我们从生成的代码角度对比两种机制。

函数指针的汇编输出

int add(int a, int b) { return a + b; }

int (*func)(int, int) = add;
int result = func(3, 4);

-O2优化下,这会生成类似以下的汇编:

; 伪汇编,ARM Cortex-M
ldr r0, =3         ; 第一个参数
ldr r1, =4         ; 第二个参数
ldr r2, =func      ; 加载函数指针
blx r2             ; 间接调用
; 结果在 r0 中

   ```

如果编译器能确定`func`指向`add`,甚至可能直接内联:

```asm
ldr r0, =3
ldr r1, =4
add r0, r0, r1     ; 直接内联 add

   ```

### std::function的汇编输出

```cpp
std::function<int(int, int)> func = add;
int result = func(3, 4);

生成的汇编更复杂:

; 伪汇编
ldr r0, =3         ; 参数
ldr r1, =4
ldr r2, =func      ; 加载std::function对象地址
ldr r3, [r2, #0]   ; 加载调用器指针(invoke函数)
blx r3             ; 间接调用invoke
; invoke内部再调用实际函数

   ```

`std::function`的调用链:

1. 调用`std::function`的内部调用器
2. 调用器调用存储的实际函数
3. 实际函数执行

**结论**:函数指针少一层间接调用,编译器更容易优化。

### 实测性能对比

```cpp
#include <benchmark>

void benchmark_function_pointer(benchmark::State& state) {
    int (*func)(int, int) = [](int a, int b) { return a + b; };
    int sum = 0;
    for (auto _ : state) {
        sum += func(1, 2);
    }
    benchmark::DoNotOptimize(sum);
}

void benchmark_std_function(benchmark::State& state) {
    std::function<int(int, int)> func = [](int a, int b) { return a + b; };
    int sum = 0;
    for (auto _ : state) {
        sum += func(1, 2);
    }
    benchmark::DoNotOptimize(sum);
}

// 典型结果(-O2优化):
// function_pointer:  ~1 ns/call(内联后)
// std_function:     ~5-10 ns/call

   ```

内联后差异可能消失,但`std::function`的内联机会更少。

<details>
<summary>查看完整可编译示例</summary>

```cpp
// Performance Comparison: Function Pointer vs std::function
// Demonstrates the performance differences between these approaches

#include <iostream>
#include <functional>
#include <chrono>
#include <vector>
#include <algorithm>

// Test function
int add(int a, int b) {
    return a + b;
}

// Direct call baseline
int test_direct(int iterations) {
    volatile int sum = 0;  // volatile to prevent optimization
    for (int i = 0; i < iterations; ++i) {
        sum += add(i, i + 1);
    }
    return sum;
}

// Function pointer call
int test_function_pointer(int iterations, int (*func)(int, int)) {
    volatile int sum = 0;
    for (int i = 0; i < iterations; ++i) {
        sum += func(i, i + 1);
    }
    return sum;
}

// std::function call
int test_std_function(int iterations, std::function<int(int, int)> func) {
    volatile int sum = 0;
    for (int i = 0; i < iterations; ++i) {
        sum += func(i, i + 1);
    }
    return sum;
}

// Lambda with auto (template)
template<typename Func>
int test_lambda_template(int iterations, Func&& func) {
    volatile int sum = 0;
    for (int i = 0; i < iterations; ++i) {
        sum += func(i, i + 1);
    }
    return sum;
}

// Benchmark helper
template<typename Func>
long long benchmark(const char* name, Func&& func, int iterations = 10000000) {
    auto start = std::chrono::high_resolution_clock::now();
    func(iterations);
    auto end = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    std::cout << name << ": " << duration.count() << " us";
    return duration.count();
}

int main() {
    std::cout << "=== Performance Comparison Demo ===" << std::endl;
    std::cout << "Compile with -O2 for meaningful results" << std::endl;
    std::cout << "Iterations: 10,000,000\n" << std::endl;

    const int iterations = 10000000;

    // Warm up
    test_direct(1000);
    test_function_pointer(1000, add);
    test_std_function(1000, add);
    test_lambda_template(1000, [](int a, int b) { return a + b; });

    // Benchmark
    auto time_direct = benchmark("Direct call", [&](int) {
        test_direct(iterations);
    });

    auto time_fp = benchmark("Function pointer", [&](int) {
        test_function_pointer(iterations, add);
    });

    auto time_sf = benchmark("std::function", [&](int) {
        test_std_function(iterations, add);
    });

    auto time_sf_lambda = benchmark("std::function (lambda)", [&](int) {
        test_std_function(iterations, [](int a, int b) { return a + b; });
    });

    auto time_template = benchmark("Template (lambda)", [&](int) {
        test_lambda_template(iterations, [](int a, int b) { return a + b; });
    });

    std::cout << "\n--- Summary ---" << std::endl;
    std::cout << "Function pointer vs Direct: "
              << (100.0 * time_fp / time_direct) << "%" << std::endl;
    std::cout << "std::function vs Direct: "
              << (100.0 * time_sf / time_direct) << "%" << std::endl;
    std::cout << "Template vs Direct: "
              << (100.0 * time_template / time_direct) << "%" << std::endl;

    // Size comparison
    std::cout << "\n--- Size Comparison ---" << std::endl;
    std::cout << "sizeof(int(*)(int,int)): " << sizeof(int(*)(int,int)) << " bytes" << std::endl;
    std::cout << "sizeof(std::function<int(int,int)>): "
              << sizeof(std::function<int(int,int)>) << " bytes" << std::endl;

    // Practical example: sorting
    std::cout << "\n--- Practical Example: Sorting ---" << std::endl;

    std::vector<int> data1(1000000);
    for (size_t i = 0; i < data1.size(); ++i) {
        data1[i] = static_cast<int>(i % 1000);
    }

    std::vector<int> data2 = data1;

    auto start1 = std::chrono::high_resolution_clock::now();
    std::sort(data1.begin(), data1.end(), [](int a, int b) { return a < b; });
    auto end1 = std::chrono::high_resolution_clock::now();

    auto start2 = std::chrono::high_resolution_clock::now();
    std::sort(data2.begin(), data2.end(), std::less<int>{});
    auto end2 = std::chrono::high_resolution_clock::now();

    auto time_sort_lambda = std::chrono::duration_cast<std::chrono::microseconds>(end1 - start1);
    auto time_sort_function = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2);

    std::cout << "Sort with lambda: " << time_sort_lambda.count() << " us" << std::endl;
    std::cout << "Sort with std::less: " << time_sort_function.count() << " ms" << std::endl;

    std::cout << "\n=== Conclusion ===" << std::endl;
    std::cout << "- Function pointers: Zero overhead after optimization" << std::endl;
    std::cout << "- std::function: Small overhead, but enables context capture" << std::endl;
    std::cout << "- Template (auto): Zero overhead, best choice when possible" << std::endl;
    std::cout << "- Use function pointers for hot paths" << std::endl;
    std::cout << "- Use std::function when you need context capture" << std::endl;
    std::cout << "- Use templates for compile-time polymorphism" << std::endl;

    return 0;
}

选择指南:什么时候用什么?

使用函数指针的场景

  1. 表驱动法:状态机、解析器、指令处理
struct OpcodeHandler {
    uint8_t opcode;
    void (*handler)(const uint8_t* args);
};

const OpcodeHandler opcode_table[] = {
    {0x01, handle_mov},
    {0x02, handle_add},
    {0x03, handle_sub},
};
  1. 中断处理表:硬件向量表
using IRQHandler = void(*)();

// 可以直接放进Flash
const IRQHandler __attribute__((section(".isr_vector"))) irq_vector[48] = {
    reinterpret_cast<IRQHandler>(__stack_top),
    reset_handler,
    nmi_handler,
    // ...
};
  1. 简单的无状态回调:不需要上下文的操作
void sort_array(int* arr, size_t len,
                int (*compare)(int, int) = default_compare);
  1. ROM驻留数据:需要放进Flash的表
const __attribute__((section(".rodata"))) DecoderEntry decoder[] = {
    {0x01, decode_instruction_a},
    {0x02, decode_instruction_b},
};

使用std::function的场景

  1. 需要捕获上下文的回调:Lambda捕获
class Device {
    int id;
    void register_callbacks() {
        manager.on_data([this](const uint8_t* data, size_t len) {
            this->handle_data(data, len);
        });
    }
};
  1. 统一接口存储多种可调用对象
class TaskScheduler {
    std::vector<std::function<void()>> tasks;
    void add_task(std::function<void()> task) {
        tasks.push_back(std::move(task));
    }
};
  1. 延迟调用/事件队列:需要存储和传递
std::queue<std::function<void()>> event_queue;

void post_event(std::function<void()> event) {
    event_queue.push(std::move(event));
}
  1. C++标准库算法:但优先用Lambda
// 可以用std::function,但Lambda更高效
std::sort(v.begin(), v.end(),
          [](int a, int b) { return a > b; });

零开销替代方案:模板化设计

如果你既想要std::function的通用性,又想要函数指针的性能,模板化设计是答案:

模板参数

// 不使用std::function,直接用模板
template<typename Callback>
void register_timer(int id, uint32_t interval_ms, Callback&& cb) {
    // 直接存储类型擦除前的原始类型
    timers[id] = Timer{interval_ms, 0, std::forward<Callback>(cb)};
}

// Timer定义为模板
template<typename Callback>
struct Timer {
    uint32_t interval;
    uint32_t elapsed;
    Callback callback;
};

// 问题:每个Callback类型实例化Timer,不能放在同一容器中

类型擦除的零开销实现

这是下一章的主题,简要说:手动实现小型类型擦除容器:

template<typename Signature>
class Callback;

template<typename R, typename... Args>
class Callback<R(Args...)> {
    // 手写小型类型擦除,避免std::function的开销
    // 具体实现见下一章
};

嵌入式最佳实践

1. 热路径用函数指针

// 高频调用:ADC采样中断
void __attribute__((interrupt)) ADC_IRQHandler() {
    static int (*handler)(int) = fast_adc_handler;  // 函数指针
    int value = *ADC_DR;
    handler(value);  // 零开销调用
}

2. 冷路径用std::function

// 低频调用:配置更新
class ConfigManager {
    void on_update(std::function<void(const Config&)> cb) {
        update_callbacks.push_back(std::move(cb));
    }
};

3. 避免std::function的动态分配

// ❌ 大Lambda捕获,会堆分配
std::function<void()> f = [big = std::array<int, 1000>()](){ /* ... */ };

// ✅ 小Lambda捕获,利用SOO
std::function<void()> f = [x = 42]() { return x * 2; };

// ✅ 或者直接用auto
auto f = [x = 42]() { return x * 2; };  // 完全零开销

4. 禁用堆时的选择

// 如果完全禁用堆,std::function可能不可用
// 使用函数指针+用户数据指针的C风格模式

struct Callback {
    void (*func)(void* user_data);
    void* user_data;
};

void register_callback(Callback cb);

// 使用
void my_handler(void* data) {
    int* counter = static_cast<int*>(data);
    (*counter)++;
}

int counter = 0;
register_callback({my_handler, &counter});

小结

std::function和函数指针各有用武之地:

特性 函数指针 std::function
开销 零(一个指针) 小到中等(2-3个指针+可能的堆)
通用性 只能指向静态/全局函数 任何可调用对象
上下文 无,需额外传递 可捕获任意上下文
类型安全 弱(容易不匹配) 强(编译期检查)
优化友好 高(易内联) 中等(类型擦除阻碍优化)
ROM友好

在嵌入式开发中的建议:

  • 优先函数指针:表驱动法、中断向量、热路径回调
  • 谨慎std::function:仅用于需要上下文捕获的冷路径
  • 考虑模板:如果能在编译期确定类型,用模板避免运行时成本
  • 手动类型擦除:对于需要两者优势的场景,考虑下一章的零开销实现

下一章我们将探讨如何实现零开销的回调机制,既保持std::function的通用性,又接近函数指针的性能。