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

C++17实现带参多可调用对象批量执行且避免数据拷贝

实现方案

要在C++17中实现一个可批量执行带参数可调用对象、且避免中间拷贝的Execute类,我们可以利用完美转发、折叠表达式和std::apply等特性,核心思路是将每个可调用对象与其参数打包为无参的调用器,存储时避免不必要的拷贝或移动。

关键设计点

  1. 避免拷贝的核心手段:
    • 对右值参数使用移动语义,而非拷贝;
    • 对需要保留引用的左值参数,用std::ref/std::cref显式传递引用,完全跳过拷贝;
    • 所有参数通过std::forward完美转发,确保传递方式符合原始值类型。
  2. 任务包装:用lambda将可调用对象和参数打包为无参调用器,后续只需调用该lambda即可执行原函数并传递参数。
  3. 批量执行:用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 22:04:55