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

时间:2015-04-08 00:55:27

标签: c++ c++11 move-semantics

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

我相信下面的static函数extractData可以实现这一点:

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

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

Marshaller marshaller;
// ... do some marshalling...
DataType marshalled_data{Marshaller::extractData(std::move(marshaller))};

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

DataType Marshaller::toDataType()
{
  return Marshaller::extractData(std::move(*this));
}

当然,这将使用:

来调用
DataType marshalled_data{marshaller.toDataType()};

...对我而言,看起来更好。但std::move(*this)事情看起来非常可疑。在调用toDataType()的情况下,marshaller无法再次使用,但我不认为编译器可以知道:函数的主体可能在来电者的编辑单位,因此无法表明marshaller已应用move()

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

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

编辑2:如果我在DataType的析构函数中放置了一个打印语句,当调用者离开作用域时,它会显示两次。如果我在DataType中包含一个数据成员,其中包含一个数组new[]delete[],那么我会获得glibc&#34;双重免费或损坏& #34;错误。所以我不确定这个如何安全,即使有几个答案说它在技术上是允许的。一个完整的答案应该解释在非平凡的DataType类中正确使用这种技术需要什么。

编辑3:这已经足够了我已经开放{1}}以解决我遗留问题的兔子洞/虫虫。

5 个答案:

答案 0 :(得分: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)();

答案 1 :(得分:8)

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

DataType Marshaller::toDataType() &&
{
    return Marshaller::extractData(std::move(*this));
}

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

// 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()};

答案 2 :(得分:6)

调用move(*this)没有任何内在的不安全因素。 move基本上只是对被调用函数的暗示,它可能会窃取对象的内部。在类型系统中,此承诺通过&&引用表达。

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

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

答案 3 :(得分:3)

我认为你不应该从*this移出,而应该移到data字段。由于这显然会使Marshaller对象处于有效但不可用的状态,因此执行此操作的成员函数本身应该在其隐式*this参数上具有右值引用限定符。

class Marshaller
{
public:
  ...
  DataType Marshaller::unwrap() &&   { return std::move(data); }

  ...
private:
  DataType data;
};

如果mMarshaller变量,请将其称为std::move(m).unwrap()。不需要任何静态成员来完成此任务。

答案 4 :(得分:2)

你写道,你想要同时摧毁Marshaller并从中删除数据。我真的不担心尝试同时做这些事情,只需先移出数据然后销毁Marshaller对象。有很多方法可以摆脱Marshaller而不考虑它,也许智能指针对你有意义吗?

重构这一点的一个选择是为DataType提供一个构造函数,该构造函数接收Marshaller并将数据移出('friend'关键字将允许您执行此操作,因为DataType将能够访问该私有'数据'变量)。

    //add this line to the Marshaller
    friend class DataType;

struct DataType
{
    DataType(Marshaller& marshaller) {
             buffer = marshaller.data.buffer;
        }

    private: 
        Type_of_buffer buffer;//buffer still needs to know how to have data moved into it
}

你也可以给它一个赋值操作符来做同样的操作(我认为这只会起作用:

DataType& operator=(Marshaller&& marshaller) {
     this.buffer = std::move(marshaller.data.buffer);
     return *this;
}

我会避免在*上使用move,因为即使它是正确的,它也会让人们失望。看起来像基于堆栈的缓冲容器可能会让你陷入困境。

你似乎担心Marshaller会在编译单元之外再次被调用。如果你有强烈的并行代码并且正在与编组人员一起快速和松散地玩游戏,或者你正在不顾一切地向你的编组人员发送指针,那么我认为你的担忧是合理的。否则,看看Marshaller是如何被移动的,并确保你已经构建了良好的对象生命周期的代码(尽可能使用对象引用)。您还可以向marshaller添加一个成员标志,指示“数据”是否已被移动,如果有人在离开后尝试访问它,则会抛出错误(如果您要并行,请确保锁定)。我只会做这个作为最后的手段或快速修复,因为它似乎不对,你的共同开发人员会想知道是什么。

如果你有时间,我有一些尼特可以选择:

  • 您的extractData方法缺少static keyword
  • 在DataType声明行
  • 上混合使用括号和括号