可以`*this`是`move()`d?

Kyl*_*and 21 c++ move-semantics c++11

我想定义一个用于编组数据的类; 当编组完成后,我想move从其中输出编组数据,这可能会使编组对象无效.

我相信这可以通过以下static功能实现extractData:

class Marshaller
{
  public:
    static DataType extractData(Marshaller&& marshaller)
    {
      return std::move(marshaller.data);
    }
  private:
    DataType data;
}
Run Code Online (Sandbox Code Playgroud)

这有点不方便打电话,但是:

Marshaller marshaller;
// ... do some marshalling...
DataType marshalled_data{Marshaller::extractData(std::move(marshaller))};
Run Code Online (Sandbox Code Playgroud)

那么我可以用成员函数包装它吗?

DataType Marshaller::toDataType()
{
  return Marshaller::extractData(std::move(*this));
}
Run Code Online (Sandbox Code Playgroud)

当然,这将使用以下方式调用:

DataType marshalled_data{marshaller.toDataType()};
Run Code Online (Sandbox Code Playgroud)

...对我来说,看起来更好.但那std::move(*this)件事看起来非常可疑.在调用的上下文中toDataType(),marshaller不能再次使用,但我不认为编译器可以知道:函数体可能在调用者的编译单元之外,所以没有什么可以表明marshaller已经move()应用于它.

这是未定义的行为吗?它完全没问题吗?还是介于两者之间?有没有更好的方法来实现相同的目标,最好不使用宏或要求调用者明确move marshaller

编辑:使用G ++和Clang ++,我发现我不仅可以编译上面的用例,而且我实际上可以继续通过编组器对底层数据进行修改,然后使用该toDataType函数重新提取修改后的数据.我还发现已经提取的数据marshalled_data继续被更改marshaller,这表明它在调用上下文marshalled_data之间共享marshaller,所以我怀疑存在内存泄漏或未定义的行为(来自双删除) .

编辑2:如果我在DataType析构函数中放置一个print语句,当调用者离开作用域时它会出现两次.如果我包括一个数据成员DataType已经在它的阵列,具有相应new[]delete[],我得到一个glibc"双自由或损坏"的错误.所以,我不知道如何可能是安全的,即使几个答案说,这在技术上是允许的.一个完整的答案应该解释在非平凡的DataType类中正确使用这种技术需要什么.

编辑3:这已经足够了一个兔子洞/虫子,我已经开辟了另一个问题来解决我剩下的问题.

Mar*_*k B 11

根据标准,移动对象仍然有效,虽然它的状态不能得到保证,所以看起来移动*this是完全有效的.是否让代码用户感到困惑完全是另一个问题.

所有这一切都说明你的真实意图是将marshallar的破坏与数据的提取联系起来.您是否考虑在单个表达式中进行所有编组操作并暂时为您处理事情?

class Marshaller
{
  public:
    Marshaller& operator()(input_data data) { marshall(data); return *this; }
    DataType operator()() { return std::move(data_); }
  private:
    DataType data_;
}

DataType my_result = Marshaller()(data1)(data2)(data3)();
Run Code Online (Sandbox Code Playgroud)

  • "根据标准,移动对象仍然有效,虽然它的状态不能得到保证,所以似乎从'*this'移动将完全有效." 该标准仅定义从标准库对象移动的状态.*我们*定义(或不​​定义)移动对象的状态.一般来说遵循标准库的规则是一个好主意,因为这种期望在我们大多数人中根深蒂固,但它既不是必需的也不是保证的. (2认同)

Ant*_*vin 8

我会避免移动*this,但如果你这样做,至少你应该在函数中添加rvalue ref-qualifier:

DataType Marshaller::toDataType() &&
{
    return Marshaller::extractData(std::move(*this));
}
Run Code Online (Sandbox Code Playgroud)

这样,用户必须像这样调用它:

// explicit move, so the user is aware that the marshaller is no longer usable
Marshaller marshaller;
DataType marshalled_data{std::move(marshaller).toDataType()};

// or it can be called for a temporary marshaller returned from some function
Marshaller getMarshaller() {...}
DataType marshalled_data{getMarshaller().toDataType()};
Run Code Online (Sandbox Code Playgroud)


CAd*_*ker 6

呼叫本身并不安全move(*this).这move实际上只是对被调用函数的暗示,它可能会窃取对象的内部.在类型系统中,此承诺通过&&引用表达.

这与破坏无关.在move不执行任何类型的破坏-如前所述,它只是使我们能够调用函数取&&的参数.接收移动对象的函数(extractData在这种情况下)也不会进行任何破坏.实际上,它需要将对象保持为"有效但未指定的状态".从本质上讲,这意味着必须能够以正常方式销毁对象(通过delete或超出范围,取决于它是如何创建的).

所以 - 如果你extractData做了它应该做的事情并将对象保留在一个允许它在以后被破坏的状态 - 对于编译器没有任何未定义或危险的事情发生.当然,代码的用户可能会遇到问题,因为对象的移动并不是很明显(以后可能不会包含任何数据).通过更改函数名称可能会更清楚一点.或者(作为另一个答案建议)通过&&对整个方法进行资格审查.