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

C++实现带参模板/成员函数入队执行的正确方案

核心问题

原有实现无法正常工作的原因如下:

  • 入队接口参数仅声明为C风格普通函数指针F(*f),无法接收成员函数指针、lambda、仿函数等其他可调用类型
  • 未适配C++成员函数的调用规则:非静态成员函数必须绑定类实例才能调用,原有逻辑没有处理隐式this参数
  • 参数传递未使用完美转发,容易出现不必要的拷贝、引用失效问题
  • 测试用例存在写法错误:window.addQueue(bar.add<int>(1,2))传入的是函数执行后的返回值,而非待执行的函数本身;全局模板函数add误将返回值写为void,本身存在语法错误;Window类定义末尾缺少分号,会直接触发编译错误。
修正实现

Window.h 完整代码

#include <SFML/Graphics.hpp>
#include <vector>
#include <functional>
#include <utility>

struct QueueEventFunction {
    std::vector<std::function<void()>> v;

    // 支持任意可调用类型入队
    template <typename F, typename... Args>
    void Enqueue(F&& f, Args&&... args)
    {
        // C++17及以上写法:完美转发捕获可调用对象和所有参数
        v.push_back([func = std::forward<F>(f), ...captured_args = std::forward<Args>(args)]() mutable {
            std::invoke(func, captured_args...);
        });
    }

    void CallAll()
    {
        for (auto& func : v) {
            func();
        }
        // 若需要执行后清空队列,取消下一行注释即可
        // v.clear();
    }
};

class Window : public sf::RenderWindow {
public:
    Window(sf::VideoMode window, const std::string& title, sf::Uint32 style = 7U, sf::ContextSettings settings = sf::ContextSettings());
    ~Window() override = default;

    template <typename F, typename... Args>
    void addQueue(F&& f, Args&&... args);

    void execQueue();
    
private:
    QueueEventFunction queue;
}; // 注意类定义末尾必须加分号,原代码遗漏了该符号

template <typename F, typename... Args>
void Window::addQueue(F&& f, Args&&... args) {
    queue.Enqueue(std::forward<F>(f), std::forward<Args>(args)...);
}

// Window构造函数、execQueue的非模板实现放到对应cpp文件即可
void Window::execQueue() {
    queue.CallAll();
}

若需要兼容C11标准,可将Enqueue内的lambda替换为std::bind实现,无需依赖C17特性:

template <typename F, typename... Args>
void Enqueue(F&& f, Args&&... args)
{
    v.push_back(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
}

测试用例正确写法(TestWindow.cpp)

#include "Window.h"

// 修正原代码返回值错误
template<typename T>
T add(T a, T b) {
    return a + b;
}

class foo{
public:
    template<typename T> T add(T a, T b){ return a + b;}
};

int main() {
    Window window(sf::VideoMode(600, 600), "Test Window",7U,sf::ContextSettings());
    
    // 1. 普通模板函数入队
    window.addQueue(add<int>, 1, 2);
    
    foo bar;
    // 2. 类成员函数入队:成员函数指针后必须传入对应类实例,再跟调用参数
    window.addQueue(&foo::add<int>, &bar, 1, 2);
    
    // 3. 也可以用lambda包装自定义逻辑,灵活度更高
    int res = 0;
    window.addQueue([&bar, &res](){ res = bar.add<int>(3,4); });
    
    // 执行队列中所有任务
    window.execQueue();
    return 0;
}
注意事项
  • 编译时需要开启对应C标准:用C17实现时加/std:c++17(MSVC)或-std=c++17(GCC/Clang);用C11兼容写法时开启C11即可。
  • 非静态成员函数不能脱离类实例单独调用,入队时必须在成员函数指针后传入对应对象的指针、引用或智能指针,std::invoke和std::bind会自动处理this指针绑定。
  • 若出现全局模板函数的链接错误,只需将模板定义移动到头文件,或在使用的编译单元内显式实例化对应类型的模板即可,和队列逻辑无关。
  • 如果需要获取函数执行的返回值,可以像示例中一样通过lambda捕获外部引用变量存储结果,入队的任务本身是无返回值的零参数函数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 02:33:26