使用std :: move传递一个临时lambda,或者"拉出"一个temp参数,有什么区别?

cod*_*der 9 c++ c++11 stdmove

我有以下(设计)代码,其中我有一个打印机类,其中包含一个打印函数和一个处理字符串然后调用回调函数到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");
}
Run Code Online (Sandbox Code Playgroud)

注意:我已经std:: move()打了两次电话......现在这对我来说很有意义(但令我惊讶的是)但我只是为了展示我正在尝试的东西.我的问题是:

  1. 我应该std::move()set_callback()函数中使用"拉出"temp,如果我使用它是真的有副本或者确实std:: move(意味着它不是真正的副本吗?
  2. 我应该std:: move()用来传入lambda ......而且这是正确的.
  3. 我想我不明白为什么这段代码可以用两个std:: moves()...这意味着我仍然不明白std:: move()在做什么 - 所以如果有人能够让我知道这里发生的事情会很棒!
  4. 我知道我可以通过价值传递,但我的目标是移动温度,以便我没有它的副本.这是完美转发的意思吗?

我的例子可以在wandbox中看到:https://wandbox.org/permlink/rJDudtg602Ybhnzi

更新 我尝试使用std :: move的原因是为了避免复制lambda.(我认为这叫做转发/完美转发)......但我想我正在制作它的哈希值!

lub*_*bgr 7

在这一行中,

w.set_callback( std::move([&](std::string s){ return p.print(s); }) );
Run Code Online (Sandbox Code Playgroud)

您将右值转换为右值。这是一个空操作,因此毫无意义。默认情况下,将临时值传递给按值接受其参数的函数是可以的。无论如何,函数参数很可能会被实例化。在最坏的情况下,它是移动构造的,不需要显式调用std::move函数参数 - 同样,因为它在您的示例中已经是一个右值。为了澄清这种情况,请考虑这种不同的情况:

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));
Run Code Online (Sandbox Code Playgroud)

现在是另一种情况。在这个片段中

void set_callback(callback_fn callback)
{
    m_callback = std::move(callback);
}
Run Code Online (Sandbox Code Playgroud)

你移动分配到m_callback. 这很好,因为参数是按值传递的,之后不会使用。有关此技术的一个很好的资源是 Eff 中的 Item 41。现代 C++。然而,在这里,Meyers 也指出,虽然使用 pass-by-value-then-move-construct 进行初始化通常是好的,但它不一定是赋值的最佳选择,因为 by-value 参数必须分配内部内存给保持新状态,而当直接从const-qualified 引用函数参数复制时,可以使用现有缓冲区。这是std::string论证的例证,我不确定如何将其转移到std::function 实例,但是当它们擦除底层类型时,我可以想象这是一个问题,尤其是对于较大的闭包。


Rei*_*ica 5

我想我不明白为什么这段代码可以与两个 std::move 一起工作......这意味着我仍然不明白 std::move 在做什么

std::move在您打算对象移动的情况下,是否有明确说明。移动语义旨在与rvalues一起使用。因此,std::move()采用任何表达式(例如左值)并从中生成右值。当您需要允许将左值传递给接受右值引用作为参数的函数重载时,通常会出现这种用法,例如移动构造函数移动赋值运算符移动的想法是有效地转移资源而不是复制。

在您的代码段中,您没有std::move()以无效方式使用,因此此代码有效。在其余的答案中,我们尝试查看这种用法是否有利。

我应该使用 std::move 传入 lambda

似乎没有,您没有理由在代码段中这样做。首先,您正在调用move()一个已经是rvalue 的东西。此外,在语法上,set_callback()正在std::function<bool(std::string)>按值接收其参数,您的 lambda 目前正在初始化一个实例。

我应该在 set_callback() 函数中使用 std::move

通过在成员变量上使用赋值运算符移动版本而不是常规赋值,您将获得什么并不是 100% 清楚的。不过,它不会导致任何未定义的行为,因为您不会在移动参数后尝试使用它。此外,由于 C++11参数 in将为rvalues移动构造,例如您的临时值,并为lvalue构造复制,例如,如果您像这样调用它:m_callbackcallbackset_callback()

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

您需要考虑的是,在您的情况下,在方法内部,移动是否比复制更好。移动涉及它自己对相关类型的移动分配的实现。我不只是在这里说 QOI,而是考虑到在移动时您需要释放m_callback在该点之前持有的任何资源,以及从构造实例移动的场景(正如我们已经介绍的那样,callback要么复制构造要么从它的参数移动构造),这增加了这个构造已经具有的成本。不确定这样的移动开销是否适用于您的情况,但是您的 lambda 仍然不是很明显的复制成本。也就是说,选择两种重载,一种采用 aconst callback_fn& callback并在内部进行复制分配,另一种采用 acallback_fn&& callback内部移动分配可以完全缓解这个潜在问题。因为在任何一个中你都没有为参数构造任何东西,总的来说你不一定会释放旧资源作为开销,因为在执行复制分配时,可以通过复制到 LHS 的现有资源来潜在地使用它们而不是在从 RHS 移动那些之前释放它。

我知道我可以通过值传递,但我的目标是移动温度,这样我就没有它的副本(完美转发?)

类型推导templateauto)的上下文中, aT&&转发引用,而不是右值引用。因此,您只需编写一次函数(模板函数,无重载),并且在内部依赖std::forward(相当于static_cast<T&&>)将确保在任何用例中,使用两个重载的上述路径在成本是左值调用的复制分配和右值调用的移动分配:

template<class T>
void set_callback(T&& callback)
{
    m_callback = std::forward<T>(callback);
}
Run Code Online (Sandbox Code Playgroud)