【问题标题】:Too many copies when binding variadic template arguments绑定可变参数模板参数时副本过多
【发布时间】:2016-01-16 21:19:35
【问题描述】:

我正在创建一个作业队列。该作业将在线程 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,它只复制一次参数。但它必须在构造函数中初始化绑定函数。

谢谢,Piotr Skotnicki。不幸的是,我没有 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 是真正的 ART。

【问题讨论】:

    标签: c++ templates c++11 bind variadic-templates


    【解决方案1】:

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

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

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

    另外两个副本是通过分配给_cbWithArgsstd::function 类型的(§ 20.8.11.2.1 [func.wrap.func.con]):

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

    18效果:function(std::forward&lt;F&gt;(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;
    };
    

    DEMO

    【讨论】:

    • 很遗憾我没有在 VC14 中编译,std::tuple 需要初始化
    • @MORTAL “很遗憾我没有在VC14中编译,std::tuple需要初始化”,你能详细说明一下吗?
    • 感谢您提供翔实的回答,我在 VC14 中运行了您的代码,但未编译,它返回错误为 tuple(67): error C2476: 'constexpr' constructor does not initialize all members。我不知道这是否可能是 VC14 的另一个错误
    • @MORTAL 很可能是一个错误
    • @softfmla 似乎将 Args() = default; 更改为 Args() {} 会使代码在 MSVC 中编译。通过使Args 可移动,您只能用移动替换副本,这仍然没有达到应有的效率
    猜你喜欢
    • 2016-12-01
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 2013-09-22
    • 2011-12-24
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多