为什么需要在使用之前对通用参考的参数进行投射?

BЈо*_*вић 6 c++ move-semantics c++11

关于通用引用讲座中,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() );
  }
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,输出是:

1
constr
bar
destr

2
constr
move constr
destr
bar
destr
Run Code Online (Sandbox Code Playgroud)

问题确实是为什么需要这个?

std::forward< T >( a ).bar();
Run Code Online (Sandbox Code Playgroud)

我尝试没有std :: forward,它似乎工作正常(输出是相同的).

同样,为什么他建议使用rvalue在函数内部移动?(答案与std :: forward相同)

void callBar( A && a )
{
  std::move(a).bar();
}
Run Code Online (Sandbox Code Playgroud)

据我所知,这两个std::movestd::forward只是强制转换为适当的类型,但这些类型转换确实需要在上面的例子?

额外:如何修改示例以生成传递给该函数的对象的副本?

BЈо*_*вић 0

讲座中说的是这样的:

void doWork( Widget&& param )
{
  ops and exprs using std::move(param)
}
Run Code Online (Sandbox Code Playgroud)

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

如果std::forward中未使用callBar,则将reallyCallBar( A& )使用 。因为aincallBar是左值引用。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 ) );
}
Run Code Online (Sandbox Code Playgroud)

由于函数std::move中没有使用reallyCallBar( A&& ra ),因此不会进入无限循环。相反,它调用采用左值引用的版本。

因此(如讲座中所解释的):

  • std::forward必须用于通用引用
  • std::move必须用于右值引用