在lecture about universal references中,Scott Meyers(大约第40分钟)表示,在使用之前,应将通用引用的对象转换为实际类型。换句话说,只要存在具有通用引用类型的模板函数,就应该在使用运算符和表达式之前使用std::forward
,否则可能会生成对象的副本。
我对此的理解如下:
#include <iostream>
struct A
{
A() { std::cout<<"constr"<<std::endl; }
A(const A&) { std::cout<<"copy constr"<<std::endl; }
A(A&&) { std::cout<<"move constr"<<std::endl; }
A& operator=(const A&) { std::cout<<"copy assign"<<std::endl; return *this; }
A& operator=(A&&) { std::cout<<"move assign"<<std::endl; return *this; }
~A() { std::cout<<"destr"<<std::endl; }
void bar()
{
std::cout<<"bar"<<std::endl;
}
};
A getA()
{
A a;
return a;
}
template< typename T >
void callBar( T && a )
{
std::forward< T >( a ).bar();
}
int main()
{
{
std::cout<<"\n1"<<std::endl;
A a;
callBar( a );
}
{
std::cout<<"\n2"<<std::endl;
callBar( getA() );
}
}
正如所料,输出是:
1
constr
bar
destr
2
constr
move constr
destr
bar
destr
真正的问题是为什么需要这个?
std::forward< T >( a ).bar();
我试过没有std :: forward,它似乎工作正常(输出相同)。
同样,为什么他建议使用rvalue在函数内部移动? (答案与std :: forward相同)
void callBar( A && a )
{
std::move(a).bar();
}
我知道std::move
和std::forward
都只是强制转换为适当的类型,但在上面的示例中是否真的需要这些强制转换?
Bonus:如何修改示例以生成传递给该函数的对象的副本?
答案 0 :(得分:2)
这是必要的,因为bar()
可能会分别为rvalues和lvalues重载。这意味着它可能会以不同的方式执行某些操作,或者不允许执行某些操作,具体取决于您是否正确地将a
描述为左值或右值,或者只是盲目地将其视为左值。目前,大多数用户不使用此功能而且没有接触它,因为最受欢迎的编译器不支持它 - 即使GCC 4.8也不支持右值*this
。但它是标准的。
答案 1 :(得分:2)
&&
对函数的参数有两种不同的用法。对于普通函数,它意味着参数是一个右值引用;对于模板函数,它意味着它可以 左值引用或左值引用:
template <class T> void f(T&&); // rvalue or lvalue
void g(T&&); // rvalue only
void g(T&) // lvalue only
void h() {
C c;
f(c); // okay: calls f(T&)
f(std::move(c)); // okay: calls f(T&&)
g(c); // error: c is not an rvalue
g(std::move(c)); // okay: move turns c into an rvalue
}
在f
和g
内,将std::forward
应用于此类参数会保留参数的左值或右值,因此通常这是将参数转发到的最安全的方法另一个功能。
答案 2 :(得分:1)
void callBar( A && a )
{
std::move(a).bar();
}
如果你有一个rvalue引用作为参数,只能绑定到rvalue ,你通常会想要使用移动语义从这个rvalue移动并把它带出来。< / p>
参数本身是左值,因为它是一个命名的东西。你可以拿它的地址。
因此,为了使其再次成为右值并能够从中移动,您可以将std::move
应用于它。如果你只是在传递参数上调用函数,我不明白为什么你有一个参数是一个右值参考。
如果您要在函数内部移动,则只想传递右值引用,这就是您必须使用std::move
的原因。
这方面的例子在这方面实际上没有多大意义。
答案 3 :(得分:0)
讲座中的内容是:
void doWork( Widget&& param )
{
ops and exprs using std::move(param)
}
SM:这意味着:如果您看到带有右值引用的代码,并且您看到使用该参数而不被move包裹,则非常可疑。
经过一番思考,我意识到这是正确的(如预期的那样)。将原始示例中的callBar
函数更改为可以证明这一点:
void reallyCallBar( A& la )
{
std::cout<<"lvalue"<<std::endl;
la.bar();
}
void reallyCallBar( A&& ra )
{
std::cout<<"rvalue"<<std::endl;
ra.bar();
}
template< typename T >
void callBar( T && a )
{
reallyCallBar( std::forward< T >( a ) );
}
如果std::forward
中未使用callBar
,则会使用reallyCallBar( A& )
。因为a
中的callBar
是左值参考。当通用引用是右值引用时,std::forward
使其成为右值。
接下来的修改进一步证明了这一点:
void reallyCallBar( A& la )
{
std::cout<<"lvalue"<<std::endl;
la.bar();
}
void reallyCallBar( A&& ra )
{
std::cout<<"rvalue"<<std::endl;
reallyCallBar( ra );
}
template< typename T >
void callBar( T && a )
{
reallyCallBar( std::forward< T >( a ) );
}
由于std::move
函数中未使用reallyCallBar( A&& ra )
,因此它无法进入无限循环。相反,它调用版本采用左值参考。
因此(如讲座中所述):
std::forward
必须用于通用引用std::move
必须用于右值参考