使用 std::move 传递临时 lambda,或使用 "pull" 传递临时参数,有什么区别?
Using std::move to pass in a temp lambda, or to "pull" out a temp parameter and what is the difference?
我有以下(设计的)代码,其中我有一台打印机 class,其中有一个打印功能和一个工作的 class,它处理一个字符串,然后调用回调函数打印函数:
#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()
调用了两次...现在这有效(令我感到惊讶),但我都只是为了展示我正在尝试的东西。我的问题是:
- 我应该在
set_callback()
函数中使用 std::move()
来 "pull" 输出温度吗?这不是真正的副本?
- 我应该使用
std:: move()
来传递 lambda 吗……这是否正确。
- 我想我不明白为什么这段代码与两个
std:: moves()
一起工作......这意味着我仍然不明白 std:: move()
在做什么 - 所以如果有人能启发我关于这里发生的事情那就太好了!
- 我知道我可以按值传递,但我的目标是移动临时文件以便我没有它的副本。 完美转发是什么意思?
我的示例可以在 wandbox 中看到:https://wandbox.org/permlink/rJDudtg602Ybhnzi
更新
我尝试使用 std::move 的原因是为了避免复制 lambda。 (我认为这叫做 forwarding/perfect-forwarding)...但我认为我正在对它进行哈希处理!
I guess I don't understand why this code works with two std::moves...
which implies that I still don't understand what std::move is doing
std::move
是为了在您打算 从 对象移动的情况下使其明确。 移动语义 旨在与右值 一起使用。因此,std::move()
接受任何表达式(例如左值)并从中生成 rvalue。当您需要允许将 lvalue 传递给接受 rvalue reference 作为参数的函数重载时,通常会出现这种用法,例如 移动构造函数 和 移动赋值运算符。 移动 的想法是有效地传输资源而不是复制。
在您的代码段中 您没有以无效的方式使用 std::move()
,因此此代码有效。在剩下的答案中,我们尝试看看这种用法是否有利。
Should I use std::move to pass in the lambda
似乎不,您没有理由在代码段中这样做。首先,你在已经是 rvalue 上调用 move()
。此外,从语法上讲,set_callback()
正在按值接收其 std::function<bool(std::string)>
参数,其中您的 lambda 目前正在初始化一个实例。
Should I use std::move in the set_callback() function
使用 move 版本的 赋值运算符 到 m_callback
成员变量,而不是常规赋值。但是,它不会导致任何未定义的行为,因为您不会在移动参数后尝试使用它。此外,由于 C++11 set_callback()
中的 callback
参数将 move constructed for rvalues 例如你的临时,并为 lvalue 构造副本,例如如果您这样称呼它:
auto func = [&](std::string s){ return p.print(s); };
w.set_callback(func);
您需要考虑的是在方法内部,在您的情况下移动是否比复制更好。移动涉及它自己的 移动赋值的实现 相关类型。我不只是在这里说 QOI,而是考虑到在移动时你需要释放任何资源 m_callback
一直保持到那个点,并且对于从构造实例移动的场景(正如我们已经介绍的 callback
已经从其参数复制构造或移动构造),这增加了该构造已经具有的成本。不确定这样的移动开销是否适用于您的情况,但您的 lambda 复制起来并不明显昂贵。也就是说,选择两种重载,一种采用 const callback_fn& callback
并在内部进行复制分配,另一种采用 callback_fn&& callback
并在内部进行移动分配,可以完全缓解这一潜在问题。在任何一个中,您都不需要为参数构造任何东西,总的来说,您不一定会释放旧资源作为开销,因为在执行复制分配时,可以通过复制到 LHS 上来潜在地使用它们现有的资源而不是在从 RHS 移动之前释放它。
I know that I can pass by value, but my goal was to move the temp so
that I don't have a copy of it (perfect forwarding?)
在类型推导(template
或auto
)的上下文中,T&&
是forwarding reference,不是右值引用。因此,您只需编写一次函数(模板函数,无重载),并且在内部依赖 std::forward
(相当于 static_cast<T&&>
)将确保在任何用例中,上述路径使用这两个重载的成本是 lvalue 调用的复制赋值和 rvalue 调用的移动赋值:
template<class T>
void set_callback(T&& callback)
{
m_callback = std::forward<T>(callback);
}
这一行,
w.set_callback( std::move([&](std::string s){ return p.print(s); }) );
您将右值转换为右值。这是空操作,因此毫无意义。默认情况下,将临时对象传递给按值接受其参数的函数是可以的。无论如何,函数参数很可能就地实例化。在最坏的情况下,它是移动构造的,不需要在函数参数上显式调用 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));
现在来看另一种情况。在这个片段中
void set_callback(callback_fn callback)
{
m_callback = std::move(callback);
}
你移动赋值给 m_callback
。这很好,因为参数是按值传递的,以后不会使用。关于此技术的一个很好的资源是 Eff 中的第 41 项。现代 C++。在这里,Meyers 还指出,然而,虽然为 初始化 采用按值传递然后移动构造通常很好,但它不一定是 [=26] 的最佳选择=]assignment,因为按值参数必须分配内部内存来保存新状态,而直接从 const
限定的引用函数参数复制时可以使用现有缓冲区。这是 std::string
参数的示例,我不确定如何将其转移到 std::function
实例,但是当它们擦除底层类型时,我可以想象这是一个问题,尤其是对于较大的闭包.
根据 c++ reference std::move
只是对右值引用的转换。
- 您不需要在
set_callback
方法中使用 std::move
。 std::function
是 CopyConstructible 和 CopyAssignable (docs), so you can just write m_callback = callback;
. If you use std::function
move constructor, the object you moved from will be "unspecified" (but still valid), in particular it might be empty (which doesn't matter because it is a temporary). You can also read this topic.
- 这里也是一样。 Lambda 表达式是临时的,在调用
set_callback
后就过期了,所以移动或复制它都没有关系。
- 代码有效,因为您的回调构建(移动构建)正确。代码没有理由不起作用。
以下是 std::move
产生影响的情况示例:
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.
我有以下(设计的)代码,其中我有一台打印机 class,其中有一个打印功能和一个工作的 class,它处理一个字符串,然后调用回调函数打印函数:
#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()
调用了两次...现在这有效(令我感到惊讶),但我都只是为了展示我正在尝试的东西。我的问题是:
- 我应该在
set_callback()
函数中使用std::move()
来 "pull" 输出温度吗?这不是真正的副本? - 我应该使用
std:: move()
来传递 lambda 吗……这是否正确。 - 我想我不明白为什么这段代码与两个
std:: moves()
一起工作......这意味着我仍然不明白std:: move()
在做什么 - 所以如果有人能启发我关于这里发生的事情那就太好了! - 我知道我可以按值传递,但我的目标是移动临时文件以便我没有它的副本。 完美转发是什么意思?
我的示例可以在 wandbox 中看到:https://wandbox.org/permlink/rJDudtg602Ybhnzi
更新 我尝试使用 std::move 的原因是为了避免复制 lambda。 (我认为这叫做 forwarding/perfect-forwarding)...但我认为我正在对它进行哈希处理!
I guess I don't understand why this code works with two std::moves... which implies that I still don't understand what std::move is doing
std::move
是为了在您打算 从 对象移动的情况下使其明确。 移动语义 旨在与右值 一起使用。因此,std::move()
接受任何表达式(例如左值)并从中生成 rvalue。当您需要允许将 lvalue 传递给接受 rvalue reference 作为参数的函数重载时,通常会出现这种用法,例如 移动构造函数 和 移动赋值运算符。 移动 的想法是有效地传输资源而不是复制。
在您的代码段中 您没有以无效的方式使用 std::move()
,因此此代码有效。在剩下的答案中,我们尝试看看这种用法是否有利。
Should I use std::move to pass in the lambda
似乎不,您没有理由在代码段中这样做。首先,你在已经是 rvalue 上调用 move()
。此外,从语法上讲,set_callback()
正在按值接收其 std::function<bool(std::string)>
参数,其中您的 lambda 目前正在初始化一个实例。
Should I use std::move in the set_callback() function
使用 move 版本的 赋值运算符 到 m_callback
成员变量,而不是常规赋值。但是,它不会导致任何未定义的行为,因为您不会在移动参数后尝试使用它。此外,由于 C++11 set_callback()
中的 callback
参数将 move constructed for rvalues 例如你的临时,并为 lvalue 构造副本,例如如果您这样称呼它:
auto func = [&](std::string s){ return p.print(s); };
w.set_callback(func);
您需要考虑的是在方法内部,在您的情况下移动是否比复制更好。移动涉及它自己的 移动赋值的实现 相关类型。我不只是在这里说 QOI,而是考虑到在移动时你需要释放任何资源 m_callback
一直保持到那个点,并且对于从构造实例移动的场景(正如我们已经介绍的 callback
已经从其参数复制构造或移动构造),这增加了该构造已经具有的成本。不确定这样的移动开销是否适用于您的情况,但您的 lambda 复制起来并不明显昂贵。也就是说,选择两种重载,一种采用 const callback_fn& callback
并在内部进行复制分配,另一种采用 callback_fn&& callback
并在内部进行移动分配,可以完全缓解这一潜在问题。在任何一个中,您都不需要为参数构造任何东西,总的来说,您不一定会释放旧资源作为开销,因为在执行复制分配时,可以通过复制到 LHS 上来潜在地使用它们现有的资源而不是在从 RHS 移动之前释放它。
I know that I can pass by value, but my goal was to move the temp so that I don't have a copy of it (perfect forwarding?)
在类型推导(template
或auto
)的上下文中,T&&
是forwarding reference,不是右值引用。因此,您只需编写一次函数(模板函数,无重载),并且在内部依赖 std::forward
(相当于 static_cast<T&&>
)将确保在任何用例中,上述路径使用这两个重载的成本是 lvalue 调用的复制赋值和 rvalue 调用的移动赋值:
template<class T>
void set_callback(T&& callback)
{
m_callback = std::forward<T>(callback);
}
这一行,
w.set_callback( std::move([&](std::string s){ return p.print(s); }) );
您将右值转换为右值。这是空操作,因此毫无意义。默认情况下,将临时对象传递给按值接受其参数的函数是可以的。无论如何,函数参数很可能就地实例化。在最坏的情况下,它是移动构造的,不需要在函数参数上显式调用 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));
现在来看另一种情况。在这个片段中
void set_callback(callback_fn callback)
{
m_callback = std::move(callback);
}
你移动赋值给 m_callback
。这很好,因为参数是按值传递的,以后不会使用。关于此技术的一个很好的资源是 Eff 中的第 41 项。现代 C++。在这里,Meyers 还指出,然而,虽然为 初始化 采用按值传递然后移动构造通常很好,但它不一定是 [=26] 的最佳选择=]assignment,因为按值参数必须分配内部内存来保存新状态,而直接从 const
限定的引用函数参数复制时可以使用现有缓冲区。这是 std::string
参数的示例,我不确定如何将其转移到 std::function
实例,但是当它们擦除底层类型时,我可以想象这是一个问题,尤其是对于较大的闭包.
根据 c++ reference std::move
只是对右值引用的转换。
- 您不需要在
set_callback
方法中使用std::move
。std::function
是 CopyConstructible 和 CopyAssignable (docs), so you can just writem_callback = callback;
. If you usestd::function
move constructor, the object you moved from will be "unspecified" (but still valid), in particular it might be empty (which doesn't matter because it is a temporary). You can also read this topic. - 这里也是一样。 Lambda 表达式是临时的,在调用
set_callback
后就过期了,所以移动或复制它都没有关系。 - 代码有效,因为您的回调构建(移动构建)正确。代码没有理由不起作用。
以下是 std::move
产生影响的情况示例:
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.