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

线程池enqueue去内存分配后触发packaged_task拷贝构造报错求助

线程池enqueue函数无内存分配优化中的拷贝错误解决

原始可运行代码

template <typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
    using return_type = decltype(f(args...));
    auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );
    auto result = task->get_future();
    {
        std::unique_lock<std::mutex> lock(m_queue_mutex);
        if (m_stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
        m_tasks.emplace([task]() { (*task)(); });
    }
    m_condition.notify_one();
    return result;
}

优化后的错误代码

template <typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
    using return_type = decltype(f(args...));
    auto task = std::packaged_task<return_type()>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );
    auto result = task.get_future();
    {
        std::unique_lock<std::mutex> lock(m_queue_mutex);
        if (m_stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
        m_tasks.emplace([task = std::move(task)]() mutable { task(); });
    }
    m_condition.notify_one();
    return result;
}

触发的错误

use of deleted function ‘std::packaged_task<_Res(_ArgTypes...)>::packaged_task(const std::packaged_task<_Res(_ArgTypes ...)&) [with _Res = void; _ArgTypes = {}]’
38 | m_tasks.emplace(task mutable { task(); });

问题原因

核心问题在于:

  • std::packaged_task是移动-only类型,拷贝构造函数被显式删除,只能通过移动语义转移所有权。
  • 线程池任务队列m_tasks通常是std::queue<std::function<void()>>类型,而C11到C20标准中,std::function要求存储的可调用对象必须可拷贝构造。
  • 尽管你用std::move将packaged_task转移到lambda捕获列表,但lambda会因持有不可拷贝的packaged_task变成移动-only类型。当尝试将该lambda放入std::function时,std::function会尝试拷贝lambda,进而触发packaged_task的拷贝构造函数,导致编译错误。

原始代码用std::make_shared包装packaged_task,本质是让lambda捕获可拷贝的std::shared_ptr,使lambda本身成为可拷贝类型,符合std::function的要求,因此可以正常运行。

解决方案

根据需求,有三种可行方案:

方案1:保留std::make_shared实现

这是最稳妥、兼容性最好的方案,虽存在一次堆分配,但代码简洁可靠,适合大多数场景。

方案2:升级到C++23标准

C++23对std::function进行了扩展,允许其持有移动-only的可调用对象。此时你的优化代码可直接编译通过,无需修改——std::function会通过移动语义接收lambda,不再尝试拷贝。

方案3:自定义移动-only任务队列

若需在C++23之前的标准下避免显式使用std::make_shared,可自定义基于抽象基类的任务包装器替代std::function:

// 抽象任务基类
class Task {
public:
    virtual ~Task() = default;
    virtual void operator()() = 0;
};

// 具体任务模板类
template <typename F>
class ConcreteTask : public Task {
public:
    explicit ConcreteTask(F&& f) : m_func(std::move(f)) {}
    void operator()() override { m_func(); }
private:
    F m_func;
};

// 任务队列改为存储unique_ptr
std::queue<std::unique_ptr<Task>> m_tasks;

// enqueue函数修改如下
template <typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
    using return_type = decltype(f(args...));
    auto task = std::packaged_task<return_type()>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );
    auto result = task.get_future();
    
    {
        std::unique_lock<std::mutex> lock(m_queue_mutex);
        if (m_stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
        m_tasks.emplace(std::make_unique<ConcreteTask<decltype(std::move(task))>>(
            std::move(task)
        ));
    }
    m_condition.notify_one();
    return result;
}

该方案通过std::unique_ptr存储任务对象,利用移动语义避免拷贝,同时保持任务队列的类型统一性。本质仍存在堆分配(std::make_unique),但和std::make_shared的开销差异不大。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 10:46:20