std::forward
最常见的用法是完美转发转发(通用)引用,例如
template<typename T>
void f(T&& param)
{
g(std::forward<T>(param)); // perfect forward to g
}
此处param
是lvalue
,std::forward
最终会将其转换为右值或左值,具体取决于与其绑定的参数。
查看definition of std::forward
from cppreference.com我发现还有rvalue
重载
template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );
任何人都可以告诉我rvalue
超载的原因吗?我看不到任何用例。如果你想将一个右值传递给一个函数,你可以按原样传递它,不需要在它上面应用std::forward
。
这与std::move
不同,在那里我看到为什么人们也想要rvalue
超载:您可以处理通用代码,在这些代码中您不知道自己是什么传递,你想要无条件支持移动语义,参见例如Why does std::move take a universal reference?
编辑为了澄清这个问题,我问为什么overload (2) from here是必要的,以及一个用例。
答案 0 :(得分:1)
好的,因为@vsoftco要求提供简洁的用例,这里是一个精致的版本(使用他的想法让“my_forward”真正看到超载被调用)。
我通过提供一个代码示例来解释“用例”,该代码示例没有prvalue无法编译或行为不同(无论这样做是否真的有用)。
我们有2 overloads for std::forward
#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个。
答案 1 :(得分:0)
这个答案是为了回答@vsoftco的评论
@DarioOO感谢您的链接。你能写一个简洁的答案吗?从你的例子来看,我还不清楚为什么还需要为rvalues定义std :: forward
简而言之:
因为没有右值专门化,以下代码将无法编译
#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
在这种情况下,参数从函数调用转发。 SmartPointers::resolve
的返回值无论Impl
的构造函数接受rvalue还是左值(因此没有编译错误,而且无论如何都被移动)都会被正确移动。
基本上你可以在任何情况下使用std::foward
,你想让代码更简单,更易读,但你必须记住2点
如果小心使用是一种强大的工具。
答案 2 :(得分:0)
之前我盯着这个问题,读过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()
的调用会返回一个右值。但是,假设代码是通用的:
template <class T>
void func(T && h) {
GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}
现在,当我们致电func
时,我们希望它与Hello
和Hello2
一起正常运行,即我们想要触发一次行动。如果我们包含外部Hello
,则仅在std::forward
的右值发生这种情况,因此我们需要它。但是......我们得到了一个妙语。当我们将Hello2
的rvalue传递给此函数时,get()的rvalue重载将返回rvalue double,因此std::forward
实际上正在接受rvalue。因此,如果没有,您将无法像上面那样编写完全通用的代码。
该死。