类模板存储任意函数对象的疑问:是否需Func参数及std::function存储合法性
嘿,我来帮你拆解这两个C++技术问题,都是关于函数存储和模板设计的常见痛点,咱们一个个来聊清楚:
问题1:未知函数签名下,将任意函数对象存储到类的成员容器
如果只是需要存储任意函数(暂时不考虑调用),最简单的方案是用标准库的std::any作为容器的元素类型。std::any可以容纳任意可拷贝构造的类型,包括普通函数指针、lambda表达式、std::function等,完全不需要知道函数的签名:
#include <any> #include <vector> #include <functional> class YourClass { private: std::vector<std::any> storedFuncs; public: template<typename Func> void addFunc(Func&& func) { storedFuncs.emplace_back(std::forward<Func>(func)); } }; // 使用示例 void foo() {} int bar(int x) { return x*2; } YourClass obj; obj.addFunc(foo); obj.addFunc(bar); obj.addFunc([](){ /* lambda */ }); obj.addFunc(std::function<void(std::string)>([](std::string s){}));
如果之后需要调用这些函数,单纯用std::any就不够了——因为调用时必须知道函数的签名。这时候可以用类型擦除技术,定义一个抽象基类,再用模板子类包装不同签名的函数:
#include <vector> #include <memory> #include <functional> // 抽象基类,定义调用接口(这里以无参无返回为例,你可以根据需求扩展) class AbstractFunction { public: virtual ~AbstractFunction() = default; virtual void invoke() = 0; }; // 模板子类,包装任意签名的函数(绑定成统一的无参无返回形式) template<typename Func, typename... Args> class FunctionWrapper : public AbstractFunction { private: std::function<void()> boundFunc; public: FunctionWrapper(Func func, Args&&... args) : boundFunc(std::bind(std::forward<Func>(func), std::forward<Args>(args)...)) {} void invoke() override { boundFunc(); } }; // 辅助函数,简化包装过程 template<typename Func, typename... Args> std::unique_ptr<AbstractFunction> wrapFunction(Func&& func, Args&&... args) { return std::make_unique<FunctionWrapper<Func, Args...>>( std::forward<Func>(func), std::forward<Args>(args)... ); } class YourClass { private: std::vector<std::unique_ptr<AbstractFunction>> storedFuncs; public: template<typename Func, typename... Args> void addFunc(Func&& func, Args&&... args) { storedFuncs.push_back(wrapFunction(std::forward<Func>(func), std::forward<Args>(args)...)); } // 调用所有存储的函数 void invokeAll() { for(auto& func : storedFuncs) { func->invoke(); } } };
这种方式可以把任意签名的函数绑定成统一的调用接口,既能存储又能安全调用。
问题2:ThreadManager类模板的Func参数必要性与std::function存储
针对你的类模板,我来分析两个核心点:
1. 是否需要将Func作为模板参数?
这完全取决于你的设计目标:
- 如果你的
ThreadManager只需要管理单一签名的函数(比如所有线程都执行void(Data&)类型的函数),那把Func作为模板参数是合理的——它能保证类型安全,避免混入不匹配的函数类型,编译期就能发现错误。 - 如果你的
ThreadManager需要支持任意签名的函数,那Func作为模板参数就不合适了——因为模板参数是编译期确定的,一个ThreadManager实例只能处理一种Func类型。这时候应该改用问题1中的类型擦除方案(比如std::vector<std::any>或抽象基类容器),去掉Func模板参数,让类更灵活。
2. 能否不指定模板参数列表直接存入std::function?
如果保留Func模板参数,默认情况下编译器无法自动推导Func类型,除非你提供模板推导指引或支持推导的构造函数/成员函数:
比如给ThreadManager添加一个能推导模板参数的构造函数:
template<class Data, class Func> class ThreadManager final { private: std::vector<std::shared_ptr<Data>> storedDataPtrs; std::vector<std::shared_ptr<std::thread>> storedThreadPtrs; std::vector<Func> storedFuncs; // 假设存储Func的容器 public: // 构造函数,用于推导模板参数 ThreadManager(std::vector<Func> funcs) : storedFuncs(std::move(funcs)) {} }; // 模板推导指引(C++17及以上支持) template<typename Data, typename Func> ThreadManager(std::vector<std::shared_ptr<Data>>, std::vector<Func>) -> ThreadManager<Data, Func>;
这样你就可以不用显式指定模板参数,直接传入std::function:
// 编译器会自动推导Data为MyData,Func为std::function<void()> ThreadManager tm{ std::vector<std::shared_ptr<MyData>>{}, {std::function<void()>([](){})} };
但如果去掉Func模板参数,改用类型擦除容器,那创建ThreadManager时只需要指定Data参数,直接就能存任意std::function,完全不需要额外的模板参数:
template<class Data> class ThreadManager final { private: std::vector<std::shared_ptr<Data>> storedDataPtrs; std::vector<std::shared_ptr<std::thread>> storedThreadPtrs; std::vector<std::any> storedFuncs; // 用std::any存储任意函数 public: template<typename Func> void addFunc(Func&& func) { storedFuncs.emplace_back(std::forward<Func>(func)); } }; // 使用示例,只需要指定Data类型 ThreadManager<MyData> tm; tm.addFunc(std::function<void()>([](){})); tm.addFunc(std::function<int(MyData&)>([](MyData& d){ return 0; }));
内容的提问来源于stack exchange,提问作者Francis Cugler
相关产品推荐
相关产品推荐

