派生类对象的移动构造函数

64

当你有一个带有移动构造函数的派生对象,而基类对象也具有移动语义时,从派生对象移动构造函数中调用基类对象移动构造函数的正确方法是什么?

我首先尝试了最显而易见的方法:

 Derived(Derived&& rval) : Base(rval)
 { }

然而,这似乎最终会调用基类对象的拷贝构造函数。然后我尝试在这里明确使用std::move,像这样:

但是,这似乎最终会调用基类对象的拷贝构造函数。然后我尝试在此处显式使用std::move,如下所示:

 Derived(Derived&& rval) : Base(std::move(rval))
 { }

这个方法起作用了,但我很困惑为什么需要这样做。我以为 std::move 只是返回一个右值引用。但由于在这个例子中 rval 已经是一个右值引用,调用 std::move 应该是多余的。但是如果我不在这里使用 std::move,它就会调用拷贝构造函数。所以为什么需要调用 std::move

3个回答

55

rval不是一个右值,而是在移动构造函数体内部作为左值。这就是为什么我们必须显式地调用std::move的原因。

请参考此文。重要的是注意到:

请注意上面的参数x在移动函数内部被视为左值,即使它被声明为右值引用参数。这就是为什么在传递到基类时需要说move(x)而不是只有x的原因。这是移动语义的一个关键安全特性,旨在防止从某个命名变量中无意中移动两次。所有移动操作都只发生在右值或使用显式转换为右值的情况下,例如使用std::move。如果你给变量取了名字,那么它就是左值。


1
无论是否使用移动构造函数,如果您将一个普通的左值作为参数输入(而不是使用std::move(xx)),则会出现“无法将‘Xyy’左值绑定到定义为foo(Xyy&& xyy)的‘Xyy&&’”的错误提示。请注意这一点。 - Kemin Zhou

3

命名的R值引用被视为L值。

因此,我们需要使用std::move将其转换为R值。


-6
你真的应该使用 std::forward(obj) 而不是 std::move(obj)。Forward 会根据 obj 是 rvalue 还是 lvalue 返回正确的值,而 move 会将一个 lvalue 变成 rvalue。

25
在这个上下文中,您总是想要将其转换为右值。 - Howard Hinnant
2
因此,真正的意图是精确地转发传递的内容,而不是更改传递的内容。Forward<type>()正是这样做的。无论传递什么,它都可以让您练习使用它来调用所有基本函数。习惯于调用move()可能会导致难以追踪的错误。如果您想在既可以作用于rvalue又可以作用于lvalue的函数上使用模板,则这非常方便。如果错误地将该函数传递给一个lvalue对象,您是否希望它继续执行?只是有点像魔鬼的辩护。 - jbreiding
1
@jbreiding 我不理解这些... 能否详细解释一下,提供一些代码示例来说明什么情况下 std::move 会失败,为什么 std::forward 更受欢迎以及何时使用哪个? - aCuria
1
@jbreiding 请参考Scott Meyers的* Effective Modern C ++ *,第23和25项。其中一部分引用如下:“ std :: forward 需要函数参数和模板类型参数…我们传递给 std :: forward 的类型应该是一个非引用类型…这意味着 std :: movestd :: forward 更省打字,它使我们免于通过传递编码了我们正在传递的参数是右值的类型参数来麻烦自己。它还消除了我们传递错误类型的可能性……这可能会导致数据成员[]被复制构造,而不是移动构造。” - Kyle Strand
@aCuria,无论何时您不再使用该对象时,std::move都将是错误的选择。当然,在移动构造函数内部从未出现这种情况,但在尝试完美转发时,如果存在对rvalue-ref的重载,则使用move会导致调用错误的函数。 - Kyle Strand
1
std::forward是用于左值与右值的“脏”重载助手。它应该在你想让函数模板能够转发两者时使用。在这里,你只需要调用父类移动构造函数,因此std::move更加明确和直接。 - mip

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