【发布时间】:2015-06-12 18:52:45
【问题描述】:
我想定义一个用于编组数据的类;编组完成后,我想 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()。
这是未定义的行为吗?完全没问题吗?或者介于两者之间?有没有更好的方法来实现相同的目标,最好不使用宏或要求调用者明确地movemarshaller?
编辑: 使用 G++ 和 Clang++,我发现我不仅可以编译上述用例,而且我实际上可以继续通过编组器对基础数据进行修改,然后使用toDataType 函数重新提取修改后的数据。我还发现marshalled_data中的already-extracted数据继续被marshaller改变,说明marshalled_data是marshaller和调用上下文共享的,所以我怀疑这里有内存泄漏或未定义的行为(来自双重删除)。
编辑 2: 如果我在 DataType 的析构函数中放入 print 语句,当调用者离开作用域时,它会出现 两次。如果我在 DataType 中包含一个包含数组的数据成员,并带有相应的 new[] 和 delete[],我会收到 glibc“双重释放或损坏”错误。所以我不确定这如何是安全的,尽管有几个答案说它在技术上是允许的。一个完整的答案应该解释正确使用这种技术与非平凡的DataType 类需要什么。
编辑 3: 这已经足够我打开 another question 来解决我剩下的问题了。
【问题讨论】:
-
假设两个
marshalled_data对象不应该引用相同的数据(因为您似乎对它们这样做感到惊讶):如果marshalled_data是共享的,那么它听起来像底层的 @987654349 @ 在其复制移动构造函数中有一个错误。例如,它可能根本没有,默认的复制构造函数正在做一个浅拷贝。 -
移动语义不具有破坏性。也就是说,无论您是否已从对象中移动,都会调用对象的析构函数。
std::move只不过是一个简单的转换,它改变了表达式的值类别。它本身不做任何事情。类类型的移动操作的语义完全由类类型定义。 -
顺便说一句,您的
extractData让我想起了unique_ptr::release或thread::detach或从其(前)经理释放资源的类似功能。你甚至不需要在这里关心移动语义;只需清楚地指出(通过函数名称及其规范/描述)它将改变/清空资源管理器。 -
右值引用仍然是像左值引用一样的引用。绑定到它们不会影响绑定的对象。左值和右值引用的区别在于你可以绑定什么样的表达式(值类别)。但是
std::move和一些类似的转换可以改变表达式的值类别。所以我们只有约定。一个右值引用per convention被假定为引用一个我们可以从中窃取资源的对象。该对象的破坏不受“窃取资源”的影响。 -
复制和交换习语的某些风格间接地做到了这一点:如果他们编写
swap(*this, other)而没有自定义交换,那么它将使用 C++11 默认实现std::swap,即三个电话给std::move
标签: c++ c++11 move-semantics