You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让C++定时器调用类的非静态成员函数

解决方案

要让Timer支持类成员函数回调,同时保持语法贴近std::thread且不使用std::function、std::bind或lambda,可以通过模板成员函数+静态包装器的方式实现,核心思路是把成员函数和对象实例绑定到Timer内部,在定时器线程中调用对应的包装函数执行成员函数。

重构后的Timer类完整代码

#include <thread>
#include <time.h>

class Timer
{
    // 存储自由函数指针
    void (*free_func)();
    // 存储成员函数调用所需的对象指针和包装器
    void* obj_ptr;
    void (*member_invoker)(void*);
    bool running;
    bool use_member; // 标记当前使用的是成员函数还是自由函数

public:
    Timer() : free_func(nullptr), obj_ptr(nullptr), member_invoker(nullptr), 
              running(false), use_member(false) {}
    ~Timer() { stop(); }

    void start();
    void stop();
    // 自由函数回调设置
    void setCallback(void (*func)());
    // 成员函数回调设置(模板版本,支持任意类的无参void成员函数)
    template <typename T, void (T::*MemFunc)()>
    void setCallback(T* instance) {
        use_member = true;
        obj_ptr = instance;
        // 绑定对应的静态包装器函数
        member_invoker = &wrapper<T, MemFunc>;
    }

private:
    // 静态包装器:把成员函数调用转换为普通函数调用,接收对象指针作为参数
    template <typename T, void (T::*MemFunc)()>
    static void wrapper(void* obj) {
        // 将void*转换回原对象类型,调用成员函数
        (static_cast<T*>(obj)->*MemFunc)();
    }

    void run();
};

// 设置自由函数回调
void Timer::setCallback(void (*func)())
{
    use_member = false;
    free_func = func;
    obj_ptr = nullptr;
    member_invoker = nullptr;
}

// 启动定时器(非阻塞)
void Timer::start()
{
    std::thread th(&Timer::run, this);
    th.detach();
}

// 停止定时器
void Timer::stop()
{
    running = false;
}

// 定时器核心循环:周期性执行回调
void Timer::run()
{
    running = true;
    const int period_ms = 100; // 固定100ms周期
    struct timespec now, then;

    while (running)
    {
        // 记录回调执行前的时间
        clock_gettime(CLOCK_MONOTONIC, &then);

        // 根据类型执行回调
        if (use_member) {
            if (member_invoker && obj_ptr) {
                member_invoker(obj_ptr);
            }
        } else {
            if (free_func) {
                free_func();
            }
        }

        // 记录回调执行后的时间
        clock_gettime(CLOCK_MONOTONIC, &now);

        // 计算剩余睡眠时间(修复原代码中未定义变量和时间计算错误)
        long long elapsed_nsec = (now.tv_sec - then.tv_sec) * 1000000000LL + (now.tv_nsec - then.tv_nsec);
        long long total_period_nsec = period_ms * 1000000LL;
        long long remaining_nsec = total_period_nsec - elapsed_nsec;

        // 仅当剩余时间为正时才睡眠,避免负时间导致错误
        if (remaining_nsec > 0) {
            struct timespec sleep_time;
            sleep_time.tv_sec = remaining_nsec / 1000000000LL;
            sleep_time.tv_nsec = remaining_nsec % 1000000000LL;
            clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep_time, nullptr);
        }
    }
}

// 示例自由函数
void tick()
{
    // 执行自定义逻辑
}

// 示例类及成员函数
class SomeClass {
public:
    void someMember() {
        // 执行自定义成员函数逻辑
    }
};

int main ()
{
    // 使用自由函数回调
    Timer timer1;
    timer1.setCallback(tick);
    timer1.start();

    // 使用成员函数回调(语法接近std::thread)
    SomeClass instance;
    Timer timer2;
    timer2.setCallback<SomeClass, &SomeClass::someMember>(&instance);
    timer2.start();

    // 保持主线程运行,避免程序退出
    while(true) {
        sleep(1);
    }
}

关键说明

  1. 模板成员函数:setCallback的模板版本接收类类型T和成员函数指针MemFunc作为模板参数,同时接收对象实例指针,把这些信息绑定到Timer内部。
  2. 静态包装器函数:wrapper是静态模板函数,负责把void*类型的对象指针转换回原类型,并调用指定的成员函数,解决了成员函数必须依赖对象实例才能调用的问题。
  3. 类型区分:用use_member标记当前使用的是成员函数还是自由函数,在run循环中分支执行对应的回调逻辑。
  4. 修复原代码问题:修正了原Timer类中未定义的ms变量和时间计算错误,确保睡眠时间计算正确。

使用方式

  • 自由函数:直接传递函数名,和原用法一致。
  • 成员函数:使用timer.setCallback<类名, &类名::成员函数>(对象指针)的语法,和std::thread传递成员函数的逻辑一致。

内容的提问来源于stack exchange,提问作者Marcell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 03:12:35