移动后右值是否有效?

Should the rvalue be valid after a move?

本文关键字:有效 是否 移动      更新时间:2023-10-16

假设我们有以下类:

class Foo
{
public:
Foo() { _bar=new Bar };
Foo(const Foo &right) { _bar=new Bar(right.bar); };
Foo(Foo &&right) { _bar=right._bar;  right.bar=new Bar(); };
~Foo() { delete _bar; }
Foo &operator=(const Foo &right) { _bar->opertor=(right.bar); return *this;}
Foo &operator=(Foo &&right) { std::swap(_bar, right._bar); return *this;}
void func() { _bar->test=1 };
private:
Bar *_bar;
};

将其更改为以下内容并期望最终用户知道在执行移动后右值不再有效(因为调用赋值运算符以外的任何内容都可能崩溃)是否合法?

class Foo
{
public:
Foo() { _bar=new Bar };
Foo(const Foo &right) { _bar=new Bar(right.bar); };
Foo(Foo &&right) { _bar=right._bar;  right.bar=nullptr; };
~Foo() { if(_bar != nullptr) delete _bar; }
Foo &operator=(const Foo &right) 
{ 
if(_bar == nullptr) 
_bar=new Bar();
_bar->opertor=(right.bar); 
return *this;
}
Foo &operator=(Foo &&right)
{
if(_bar != nullptr)
delete _bar;  
_bar=right._bar; 
right._bar=nullptr; 
return *this;
}
void func() { _bar->test=1 };
private:
Bar *_bar;
};

我的担忧来自这样一个事实,即 func(以及类中的所有其他函数)假设_bar存在。

原则上,它可能会变得无效,尽管您可能需要考虑将其保留为可分配状态(您的原始实现(因此经过编辑)没有这样做)。这将遵循标准库的政策,该政策规定:

除非另有指定,否则所有已从中移动的标准库对象都将处于有效但未指定的状态。也就是说,只有没有前置条件的函数(如赋值运算符)才能在对象从中移动后安全地在对象上使用

我建议重新实现赋值运算符,以便它将"this"对象与新构造的对象交换。这通常是避免在实现分配时引入错误行为的好方法。

移自对象应处于有效但未指定的状态。 请注意,这是一个建议,但不是标准的绝对要求。

如果之后对第二个代码执行正常操作(特别是复制赋值运算符),则第二个代码将中断。

如果_bar == nullptr是有效状态,则您的复制赋值运算符有错误; 如果它不是一个有效的状态,那么我会说你的移动构造函数被窃听了。

铌。在第二个代码中,析构函数中的if检查是多余的,因为删除空指针是合法的。

将其

更改为以下内容并期望最终用户知道在执行移动后右值不再有效是否合法?

你当然应该这样做。不应将右值引用保持在可用状态。移动构造函数和移动赋值运算符背后的想法是,您正在从对象中移动有用的数据。不过,我不会再用"不再有效"来描述它。从C++的角度来看,它仍然是一个有效的对象,就像nullptr是一个有效的指针一样。

第二个版本更惯用。从对象移出后,假定将不再使用该对象。

在指针上调用delete之前检查nullptr是不必要的,因为它在标准中定义为不执行任何操作。

如果用户希望使用移自对象,他的工作是确保它处于有效状态(即从有效对象进行赋值)。

您应该为Foo记录每个成员函数具有的前提条件,例如:

void Foo::func();

要求:*this未处于移出状态。

然后,您可以为需要使用Foo::func()的任何客户提供服务,除非...

如果您将Foo与需要func()的其他人的库一起使用,并且不记录类似"除非处于移出状态"之类的内容,那么您就不走运了。

例如:假设您的Foo具有如下operator<

bool operator<(const Foo& x, const Foo& y);

要求:xy都不能处于移出状态。

现在,如果您执行以下操作:

std::vector<Foo> v;
// ... fill v
std::sort(v.begin(), v.end());  // oops!

上面的最后一行要求FooLessThanComparableFoo是否处于移自状态。 std::lib 中的所有通用代码都是如此。 std 代码的 require 子句适用于用户提供的代码,并且不会对移自对象进行例外。

但是,如果operator<在任一参数处于移自状态时正常工作,则调用std::sort就可以了。 即使func()仍然要求Foo处于移出状态,也是如此。 这是因为std::sort根本不需要func()工作。

因此,总而言之,这完全取决于您Foo与哪些其他代码交互。 也许没关系,也许不是。 记录Foo的作用,并了解Foo使用的代码的要求。