[英]Are there any use cases for std::forward with a prvalue?
std::forward
的最常见用法是,完美转发转发(通用)引用,例如
template<typename T>
void f(T&& param)
{
g(std::forward<T>(param)); // perfect forward to g
}
这里param
是一个lvalue
, std::forward
最终将它转换为rvalue或lvalue,具体取决于与它绑定的参数。
从cppreference.com看到std::forward
的定义我发现还有一个rvalue
重载
template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );
任何人都可以给我任何理由为什么rvalue
超载? 我看不到任何用例。 如果要将rvalue传递给函数,可以按原样传递它,不需要在其上应用std::forward
。
这与std::move
不同,我在这里看到为什么还要一个rvalue
重载:你可能会处理通用代码,在这些代码中你不知道你传递了什么,并且你想要无条件支持移动语义,参见eg 为什么std :: move采用通用引用? 。
好的,因为@vsoftco要求简洁的用例,这里是一个精致的版本(使用他的想法让“my_forward”真正看到过载被调用)。
我通过提供一个代码示例来解释“用例”,该代码示例没有prvalue无法编译或行为不同(无论这样做是否真的有用)。
我们有std::forward
2次重载
#include <iostream>
template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type& t) noexcept
{
std::cout<<"overload 1"<<std::endl;
return static_cast<T&&>(t);
}
template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type&& t) noexcept
{
std::cout<<"overload 2"<<std::endl;
static_assert(!std::is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
我们有4个可能的用例
用例1
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &&
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(v)); // &
return 0;
}
用例2
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &&
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(std::move(v))); //&&
return 0;
}
用例3
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &
Library( vector<int> a):b(a){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(v)); // &
return 0;
}
用例4
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &
Library( vector<int> a):b(a){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(std::move(v))); //&&
return 0;
}
这是简历
请注意,如果我们不使用前进
Library a( std::move(v));
//and
Library a( v);
你得到:
如你所见,如果你只使用两个forward
重载中的一个,你基本上导致不能编译4个案例中的2个,而如果你根本不使用forward
,你将只能编译4个案例中的3个。
这个答案是为了回答@vsoftco的评论
@DarioOO感谢您的链接。 你能写一个简洁的答案吗? 从你的例子来看,我仍然不清楚为什么还需要为rvalues定义std :: forward
简而言之:
因为没有rvalue专门化,以下代码将无法编译
#include <utility>
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// hi! only rvalue here :)
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v));
return 0;
}
但是我无法拒绝输入更多,所以这里的答案也不是简单的版本。
长版:
您需要移动v
因为Library
没有构造函数接受左值,但只有rvalue引用。 如果没有完美的转发,我们最终会出现不良行为:
传递重物时,包裹功能会产生高性能的阴谋。
使用移动语义,我们确保使用移动构造函数,如果可能的话。 在上面的例子中,如果我们删除std::forward
,代码将无法编译。
那么,什么是真正在做forward
? 没有我们的共识,移动元素? 不!
它只是创建矢量的副本并移动它。 我们怎么能确定呢? 只需尝试访问该元素。
vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v)); //what happens here? make a copy and move
std::cout<<v[0]; // OK! std::forward just "adapted" our vector
如果您改为移动该元素
vector<int> v;
v.push_back(1);
A a( std::move(v)); //what happens here? just moved
std::cout<<v[0]; // OUCH! out of bounds exception
因此需要重载才能实现仍然安全的隐式转换,但如果没有重载则不可能。
事实上,以下代码将无法编译:
vector<int> v;
v.push_back(1);
A a( v); //try to copy, but not find a lvalue constructor
真实用例:
您可能会争辩说转发参数可能会创建无用的副本,从而隐藏可能的性能损失,是的,这实际上是正确的,但请考虑实际用例:
template< typename Impl, typename... SmartPointers>
static std::shared_ptr<void>
instancesFactoryFunction( priv::Context * ctx){
return std::static_pointer_cast<void>( std::make_shared<Impl>(
std::forward< typename SmartPointers::pointerType>(
SmartPointers::resolve(ctx))...
) );
}
代码取自我的框架(第80行): Infectorpp 2
在这种情况下,参数从函数调用转发。 无论Impl
构造函数接受rvalue还是左值(因此没有编译错误和那些都被移动), SmartPointers::resolve
的返回值都被正确移动。
基本上你可以在任何情况下使用std::foward
,你想让代码更简单,更可读,但你必须记住2点
如果小心使用是一个强大的工具。
我之前盯着这个问题,读过Howard Hinnant的链接,经过一个小时的思考后无法完全理解它。 现在我正在寻找并在五分钟内得到答案。 (编辑:得到答案太慷慨了,因为Hinnant的链接有答案。我的意思是我理解,并且能够以更简单的方式解释它,希望有人会发现有用的)。
基本上,这允许您在某些情况下是通用的,具体取决于传入的类型。请考虑以下代码:
#include <utility>
#include <vector>
#include <iostream>
using namespace std;
class GoodBye
{
double b;
public:
GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
GoodBye( const double& a):b(a){ std::cerr << "copy"; }
};
struct Hello {
double m_x;
double & get() { return m_x; }
};
int main()
{
Hello h;
GoodBye a(std::forward<double>(std::move(h).get()));
return 0;
}
此代码打印“移动”。 有趣的是,如果我删除std::forward
,它会打印副本。 对我来说,这很难让我全神贯注,但让我们接受并继续前进。 (编辑:我想这会发生,因为get会返回一个左值引用到rvalue。这样的实体衰变成一个左值,但是std :: forward会把它变成一个右值,就像常用的forward一样。仍觉得不直观虽然)。
现在,让我们想象另一个类:
struct Hello2 {
double m_x;
double & get() & { return m_x; }
double && get() && { return std::move(m_x); }
};
假设在main
的代码中, h
是Hello2的一个实例。 现在,我们不再需要std :: forward,因为对std::move(h).get()
的调用返回一个rvalue。 但是,假设代码是通用的:
template <class T>
void func(T && h) {
GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}
现在,当我们调用func
,我们希望它与Hello
和Hello2
一起正常工作,即我们想触发一个移动。 如果我们包含外部std::forward
,那只会发生对于Hello
的右值,所以我们需要它。 但是......我们得到了一个妙语。 当我们将Hello2
的rvalue传递给this函数时,get()的rvalue重载将返回一个rvalue double,因此std::forward
实际上接受了一个rvalue。 因此,如果没有,您将无法像上面那样编写完全通用的代码。
该死的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.