实现移动构造函数

4
在被 *** 注释的那一行中,为什么会调用 Bar 的复制构造函数?input_bar 是一个右值引用,因此我预计会调用移动构造函数。它转换为左值引用了吗?如果我将那一行改为 bar_(std::move(input_bar)),就可以调用移动构造函数了。
#include <iostream>
#include <array>
#include <memory>

class Bar
{
public:   
  Bar(const Bar& bar)
  {
    std::cout << "copy constructor called" << std::endl;
  }

  Bar(Bar&& bar)
  {
    std::cout << "move constructor called" << std::endl;
  }
};

class Foo
{
public:
  Foo(Bar&& input_bar) :
    bar_(input_bar) // ***
  {
  }
  Bar bar_;
};

int main()
{
  Bar bar;
  Foo foo(std::move(bar));
  return 0;
}

请查看 https://dev59.com/gl4c5IYBdhLWcg3wx8pU - Jonathan Wakely
2个回答

9

一旦实体有名称,它就明确是lvalue!如果你有一个rvalue引用的名称,具有该名称的实体不是rvalue而是lvalue。整个重点在于你知道该实体引用一个rvalue,并且你可以合法地移动其内容。

如果你想把rvalueness转发给下一个要调用的函数,你应该使用std::move(),如:

Foo(Bar&& bar): bar_(std::move(bar)) {}

没有使用std::move(),rvalue被认为是由构造函数所拥有的。使用std::move()会释放所有权,并将其传递给下一个函数。

标准参考资料会很好,但并非完全必要。 - Captain Obvlious
我明白了。帮助我理解的是,右值引用(即input_bar)是对右值的引用,因此它是左值。我知道这听起来很显然... - Agrim Pathak

2

您需要移动rhrs:

  Foo(Bar&& input_bar) :
    bar_(std::move(input_bar)) // ***
  {
  }

这样做的原因是一旦我们实际使用RHR,它应该被视为超出范围。强制使用std::move可以使以下代码不会未定义:

 Foo(Bar&& input_bar) {
    std::cout << input_bar.baz << std::endl;//not undefined
    bar_ = Bar{std::move(input_bar)};//input_bar is now essentailly destroyted
    //std::cout << input_bar.baz << std::endl;//Undefined behavior, don't do this
 }

一般的经验法则是,只有没有名称的东西才能被用作RHR(右手规则)...作为参数的RHR具有名称,因此在调用函数之前将被视为LHR(左手规则)。


是的,正如楼上已经发现的那样。楼主正在询问为什么需要这样做,为什么不使用std::move就无法实现该效果。 - user743382
最后一行是否产生未定义的行为取决于baz是什么。对于大多数类型,我不认为这会导致未定义的行为,只是未指定的行为。 - Benjamin Lindley
对于@BenjaminLindley的评论,要补充一点,没有标准库类型在移动后不再可用,就我所知,大多数用户类型都遵循相同的设计。对于这样的类型,在bar_ = Bar{std::move(input_bar)};(或更好的是,只需bar_ = std::move(input_bar);)之后,input_bar处于未指定的有效状态。对于像列表这样的东西,通常意味着它被清空了,但检查列表是可以的,并且明确地清除它并添加新元素也是可以的。 - user743382

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接