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
相关产品推荐
相关产品推荐

