C++17实现带参多可调用对象批量执行且避免数据拷贝
实现方案
要在C++17中实现一个可批量执行带参数可调用对象、且避免中间拷贝的Execute类,我们可以利用完美转发、折叠表达式和std::apply等特性,核心思路是将每个可调用对象与其参数打包为无参的调用器,存储时避免不必要的拷贝或移动。
关键设计点
- 避免拷贝的核心手段:
- 对右值参数使用移动语义,而非拷贝;
- 对需要保留引用的左值参数,用
std::ref/std::cref显式传递引用,完全跳过拷贝; - 所有参数通过
std::forward完美转发,确保传递方式符合原始值类型。
- 任务包装:用lambda将可调用对象和参数打包为无参调用器,后续只需调用该lambda即可执行原函数并传递参数。
- 批量执行:用
tuple存储所有任务,通过折叠表达式遍历执行。
完整实现代码
#include <tuple> #include <functional> #include <utility> #include <iostream> // 辅助函数:将可调用对象与参数打包为无参调用器 template <typename Callable, typename... Args> auto task(Callable&& callable, Args&&... args) { return [ callable = std::forward<Callable>(callable), args = std::make_tuple(std::forward<Args>(args)...) ]() mutable { std::apply(std::move(callable), std::move(args)); }; } // 特化:处理无参数的可调用对象 template <typename Callable> auto task(Callable&& callable) { return [callable = std::forward<Callable>(callable)]() mutable { std::move(callable)(); }; } // Execute类:存储并批量执行任务 template <typename... Tasks> class Execute { private: std::tuple<Tasks...> tasks_; public: // 完美转发接收所有任务 explicit Execute(Tasks&&... tasks) : tasks_(std::forward<Tasks>(tasks)...) {} // 依次执行所有任务 void run() { std::apply([](auto&&... task_list) { (std::forward<decltype(task_list)>(task_list)(), ...); }, tasks_); } }; // 推导指引:让编译器自动推导模板参数 template <typename... Tasks> Execute(Tasks&&...) -> Execute<Tasks...>; // 测试示例 void f0() { std::cout << "f0 called\n"; } void f1(int a) { std::cout << "f1 called with a=" << a << '\n'; } void f2(int a, int b) { std::cout << "f2 called with a=" << a << ", b=" << b << '\n'; } struct BigObject { BigObject() { std::cout << "BigObject constructed\n"; } BigObject(BigObject&&) { std::cout << "BigObject moved\n"; } BigObject(const BigObject&) = delete; // 禁止拷贝 void operator()() const { std::cout << "BigObject called\n"; } }; int main() { // 基础用法:执行普通函数 Execute exec(task(f0), task(f1, 4), task(f2, 1, 2)); exec.run(); // 测试引用传递:避免左值拷贝 int x = 10, y = 20; Execute exec_ref(task(f1, std::ref(x)), task(f2, std::ref(x), std::ref(y))); x = 100; y = 200; exec_ref.run(); // 测试移动语义:无拷贝传递临时对象 Execute exec_move(task(BigObject{})); exec_move.run(); }
代码说明
task辅助函数:负责将任意可调用对象(函数、lambda、仿函数等)与其参数打包为无参lambda。通过std::forward完美转发参数,右值自动移动,左值可通过std::ref转为引用。Execute类:用tuple存储所有打包后的任务,run()方法通过std::apply和折叠表达式依次执行每个任务。- 避免拷贝验证:
- 传递临时
BigObject时,仅触发构造和移动,无拷贝(因为拷贝构造被禁用); - 用
std::ref传递左值x、y时,修改原值后执行任务会使用最新值,说明存储的是引用而非拷贝。
- 传递临时
关于期望接口的说明
你期望的Execute exec({f0}, {f1, 4}, {f2, 1, 2});写法在C中无法直接实现——初始化列表作为函数参数无法被模板推导。改用task()辅助函数的写法是符合C语法的等价替代,且能保证类型安全和无拷贝特性。
内容的提问来源于stack exchange,提问作者TruLa
相关产品推荐
相关产品推荐

