Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can `*this` be `move()`d?

Tags:

I would like to define a class for marshalling data; when marshalling is finished, I would like to move the marshalled data out from within it, which will probably invalidate the marshalling object.

I believe this is possible with the static function extractData below:

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

This is a bit inconvenient to call, though:

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

So can I wrap it with a member function?

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

This would, of course, be called using:

DataType marshalled_data{marshaller.toDataType()}; 

...which, to me, looks much nicer. But that std::move(*this) thing looks awfully suspicious. In the context of the call to toDataType(), marshaller can't be used again, but I don't think the compiler can know that: the body of the function could be outside the caller's compilation unit, so there's nothing to indicate that marshaller has had move() applied to it.

Is this undefined behavior? Is it perfectly fine? Or somewhere in between? Is there a nicer way to accomplish the same goal, preferably without using a macro or requiring the caller to explicitly move marshaller?

EDIT: With both G++ and Clang++, I found that not only could I compile the above use case, but I could actually continue to make modifications to the underlying data via the marshaller, then re-extract the modified data using the toDataType function. I also found that the already-extracted data in marshalled_data continued to be changed by marshaller, which indicates that the marshalled_data is shared between the marshaller and the calling context, so I suspect that there is either a memory-leak or undefined behavior (from double-deletion) here.

EDIT 2: If I put a print statement in DataType's destructor, it appears twice when the caller leaves scope. If I include a data member in DataType that has an array in it, with a corresponding new[] and delete[], I get a glibc "double free or corruption" error. So I'm not sure how this could be safe, even though several answers have said that it's technically allowed. A complete answer should explain what is required to use this technique correctly with a non-trivial DataType class.

EDIT 3: This is enough of a rabbit-hole/can-of-worms that I've opened up another question to address my remaining concerns.

like image 921
Kyle Strand Avatar asked Apr 08 '15 00:04

Kyle Strand


People also ask

Does STD copy move?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.

What does move () do?

Move Constructor And Semantics: std::move() is a function used to convert an lvalue reference into the rvalue reference. Used to move the resources from a source object i.e. for efficient transfer of resources from one object to another.

What is Move semantics in C++?

Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and whose resources can safely be taken from that temporary object and used by another.


2 Answers

According to the standard the move-from object will still be valid although its state is not guaranteed, so it seems that moving from *this would be perfectly valid. Whether it's confusing to users of your code is another question entirely.

All that said it sounds like your real intention is to link the destruction of the marshallar with the extraction of the data. Did you consider doing all the marshalling within a single expression and letting a temporary take care of things for you?

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)(); 
like image 109
Mark B Avatar answered Oct 13 '22 16:10

Mark B


I would avoid moving from *this, but if you do it, at least you should add rvalue ref-qualifier to the function:

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

This way, the user will have to call it like 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()}; 
like image 38
Anton Savin Avatar answered Oct 13 '22 14:10

Anton Savin