代码之家  ›  专栏  ›  技术社区  ›  code_fodder

使用std::move传入一个temp lambda,或者“拉出”一个temp参数,这两者有什么区别?

  •  9
  • code_fodder  · 技术社区  · 7 年前

    我有以下(人为的)代码,其中有一个printer类,其中有一个print函数,还有一个处理字符串的工作类,然后调用print函数的回调函数:

    #include <functional>
    #include <iostream>
    
    using callback_fn = std::function<bool(std::string)>;
    
    class printer
    {
    public:   
        bool print(std::string data)
        {
            std::cout << data << std::endl;
            return true;
        }
    };
    
    class worker
    {
    public:   
        callback_fn m_callback;
        void set_callback(callback_fn callback)
        {
            m_callback = std::move(callback);  // <-- 1. callback is a temp, so what does std::move do here?
        }
        void process_data(std::string data)
        {
            if (!m_callback(data)) { /* do error handling */ }
        }
    };
    
    int main() {
        printer p;
        worker w;
    
        w.set_callback( std::move([&](std::string s){ return p.print(s); }) ); // <-- 2. what does std::move do here?
        w.process_data("hello world2");
    }
    

    我有 std:: move() 打了两次电话。。。现在这是可行的(我很惊讶),但我都只是为了显示我正在努力。我的问题是:

    1. 我应该用吗 std::move() set_callback() 函数来“拉”出temp,如果我使用这个函数,是否真的有一个副本 std:: move(
    2. 我应该用吗 标准::移动()
    3. 我想我不明白为什么这个代码和两个 std:: moves() ... 这意味着我仍然不明白 标准::移动()
    4. 我知道我可以通过值传递,但是我的目标是移动temp,这样我就没有它的副本了。这就是我的意思吗 完美转发 ?

    https://wandbox.org/permlink/rJDudtg602Ybhnzi

    更新

    3 回复  |  直到 7 年前
        1
  •  5
  •   Geezer    5 年前

    我想我不明白为什么这段代码可以处理两个std::moves。。。 这意味着我仍然不明白std::move在做什么

    std::move 一个物体。 设计用于 . 因此, std::move() 接受任何表达式(如左值)并生成 R值 左值 传递给接受 作为论据,例如 移动构造函数 移动

    标准::移动() ,因此此代码有效。在剩下的答案中,我们试图看看这种用法是否有利。

    我应该使用std::move传递lambda吗

    似乎没有,你没有理由在片段中这样做。首先, 你在打电话吗 move() R值 . 此外,在句法上, set_callback() 正在接收 std::function<bool(std::string)> 参数,此时lambda正在初始化一个实例。

    我应该在set\u callback()函数中使用std::move吗

    的版本 m_callback 成员变量,而不是常规赋值。不过,它不会导致任何未定义的行为,因为移动参数后您不会尝试使用该参数。此外,由于C++ 11 callback 设置回调() 移动构造 对于 R值 如你的临时,和副本建设的一个 ,比如你这样称呼它:

    auto func = [&](std::string s){ return p.print(s); };
    w.set_callback(func);
    

    移动涉及到它自己执行 移动工作分配 对于相关类型。我在这里不仅仅是说QOI,而是考虑到当你搬家的时候你需要释放任何资源 m\ U回拨 对于从构造实例移动的场景(我们已经讨论过了) 回拨 要么是复制构造的,要么是移动构造的,这就增加了这个构造已经拥有的成本。不确定这样的移动开销是否适用于您的情况,但您的lambda显然并不像它那样昂贵。也就是说,选择两个重载,一个是 const callback_fn& callback 一个在里面,一个在 callback_fn&& callback 而向内部移动将允许完全缓解这一潜在问题。因为在任何一种情况下,都不需要为参数构造任何内容,而且总的来说,也不一定释放旧资源作为开销,因为在执行复制分配时,可以通过复制到LHS的现有资源上,而不是在从RHS移动资源之前释放资源,从而潜在地使用LHS的现有资源。

    我没有它的副本(完美转发?)

    类型推导 ( template auto ),一个 T&& 是一个 forwarding reference ,不是 std::forward (相当于 static_cast<T&&> )将确保在任何用例中,上述使用这两个重载的路径都被保留,代价是为一个 左值 呼叫和移动任务 电话:

    template<class T>
    void set_callback(T&& callback)
    {
        m_callback = std::forward<T>(callback);
    }
    
        2
  •  7
  •   Angew is no longer proud of SO    7 年前

    在这条线上,

    w.set_callback( std::move([&](std::string s){ return p.print(s); }) );
    

    你把右值转换成右值。这是一个没有行动,因此毫无意义。默认情况下,将临时变量传递给按值接受其参数的函数可以。不管怎样,函数参数都可能被实例化。在最坏的情况下,它是move构造的,不需要显式调用 std::move 在函数参数上-同样,在您的示例中它已经是一个r值。为了说明情况,请考虑以下不同的场景:

    std::function<bool(std::string)> lValueFct = [&](std::string s){ /* ... */ }
    
    // Now it makes sense to cast the function to an rvalue (don't use it afterwards!)
    w.set_callback(std::move(lValueFct));
    

    void set_callback(callback_fn callback)
    {
        m_callback = std::move(callback);
    }
    

    m_callback . 这很好,因为参数是按值传递的,以后不使用。关于这项技术的一个很好的资源是Eff中的41项。现代C++然而,在这里,Meyers还指出,虽然使用pass-by-value然后move-construct通常是好的 分配 ,因为by value参数必须分配内部内存来保存新状态,而直接从 const -限定的引用函数参数。这是一个例子 std::string 争论,我不知道这怎么能转移到 std::function 实例,但当它们删除底层类型时,我可以想象这是一个问题,尤其是对于更大的闭包。

        3
  •  1
  •   pptaszni    7 年前

    根据 c++ reference std::move 只是对右值的转换引用。

    1. 标准::移动 在你的 set_callback 方法。 std::function 是可复制的和可复制的( docs ),这样你就可以写了 m_callback = callback; . 如果你使用 std::函数 移动构造函数时,从中移动的对象将是“未指定”(但仍然有效),特别是它可能是空的(这不重要,因为它是临时的)。你也可以阅读 this topic
    2. 这里也一样。Lambda表达式是临时表达式,在调用 设置\u回调 ,所以不管你是移动它还是复制它。
    3. 代码可以工作,因为回调的构造(move-constructed)是正确的。代码没有理由不起作用。

    下面是一个例子 与众不同:

    callback_fn f1 = [](std::string s) {std::cout << s << std::endl; return true; };
    callback_fn f2 = f1;
    f1("xxx");  // OK, invokes previously assigned lambda
    f2("xxx");  // OK, invokes the same as f1
    f2 = std::move(f1);
    f1("xxx");  // exception, f1 is unspecified after move
    

    输出:

    xxx
    xxx
    C++ exception with description "bad_function_call" thrown in the test body.