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.
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.
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.
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.
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)();
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()};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With