绑定可变参数模板参数时副本过多

Too many copies when binding variadic template arguments

本文关键字:参数 副本 变参 绑定      更新时间:2023-10-16

我正在创建一个作业队列。作业将在线程 A 中创建,然后将作业发送到线程 B,线程 B 将完成作业。作业完成后,作业将被发送回线程 A。

#include <functional>
#include <iostream>
#include <memory>
using namespace std;
template<typename T, typename... Args>
class Job
{
    public:
        Job(std::weak_ptr<T> &&wp, std::function<void(const Args&...)> &&cb)
            : _cb(std::move(cb)), 
              _cbWithArgs(), 
              _owner(std::move(wp)) {}
    public:
        template<typename... RfTs>
        void bind(RfTs&&... args)
        {
            // bind will copy args for three times.
            _cbWithArgs = std::bind(_cb, std::forward<RfTs>(args)...);
        }
        void fire()
        {
            auto sp = _owner.lock();
            if (sp)
            {
                _cbWithArgs();
            }
        }
    private:
        std::function<void(const Args& ...)> _cb;
        std::function<void()> _cbWithArgs;
        std::weak_ptr<T> _owner;
};
struct Args
{
    Args() = default;
    Args(const Args &args)
    {
        cout << "Copied" << endl;
    }
};
struct Foo
{
    void show(const Args &)
    {
        cout << "Foo" << endl;
    }
};
int main()
{
    using namespace std::placeholders;
    shared_ptr<Foo> sf (new Foo());
    Args args;
    // Let's say here thread A created the job.
    Job<Foo, Args> job(sf, std::bind(&Foo::show, sf.get(), _1));
    // Here thread B has finished the job and bind the result to the
    // job. 
    job.bind(args);
    // Here, thread A will check the result.
    job.fire();
}

上面的代码编译和工作。但它给出了以下结果(g++ 4.8.4 和 clang 有相同的结果(:

Copied
Copied
Copied
Foo

有三份!不可接受,我不知道我哪里做错了。为什么是三份?我用谷歌搜索并从这里找到了一种方法:https://stackoverflow.com/a/16868401,它只复制一次参数。但它必须在构造函数中初始化绑定函数。

谢谢,彼得·斯科特尼基。不幸的是,我没有 C++14 编译器。因此,让我们使 Args 可移动:

struct Args
{
    Args() = default;
    Args(const Args &args)
    {
        cout << "Copied" << endl;
    }
    Args(Args &&) = default;
    Args& operator=(Args &&) = default;
};

现在,它只复制一次:)

最后,我采用了这个线程 https://stackoverflow.com/a/16868151/5459549 的代码。我必须说,模板gen_seq是真正的艺术。

第一个副本由std::bind自己制作:

std::bind(_cb, std::forward<RfTs>(args)...)

因为它需要存储其参数的衰减副本。

另外两份副本由转让给_cbWithArgs制作,类型为std::function(§ 20.8.11.2.1 [func.wrap.func.con](:

template<class F> function& operator=(F&& f);

18效果: function(std::forward<F>(f)).swap(*this);

哪里:

template<class F> function(F f);

9 [...] *this 以使用 std::move(f) 初始化的f的副本为目标。

也就是说,一个副本用于构造函数的参数,另一个副本用于存储参数f

由于Args是不可移动的类型,因此尝试从调用的结果移动到std::bind将回退到副本。

在您的代码中将作业执行的结果存储在std::function中似乎不合理。相反,您可以将这些值存储在元组中:

template <typename T, typename... Args>
class Job
{
public:
    Job(std::weak_ptr<T> wp, std::function<void(const Args&...)> &&cb)
        : _cb(std::move(cb)), 
          _owner(wp) {}
public:
    template<typename... RfTs>
    void bind(RfTs&&... args)
    {
        _args = std::forward_as_tuple(std::forward<RfTs>(args)...);
    }
    void fire()
    {
        auto sp = _owner.lock();
        if (sp)
        {
            apply(std::index_sequence_for<Args...>{});
        }
    }
private:    
    template <std::size_t... Is>
    void apply(std::index_sequence<Is...>)
    {
        _cb(std::get<Is>(_args)...);
    }
    std::function<void(const Args&...)> _cb;
    std::weak_ptr<T> _owner;
    std::tuple<Args...> _args;
};

演示