Hi, as I've asked in here and after a while I've agreed and accepted right answer to that question I was just thinking, if would it be useful to have something like "moving destructor" which would be invoked on moved object everytime we used move ctor or operator=.
In this way we would have to specify only in move dtor what we want from it and how our object shuld be nullified after being used by move constructor. Without this semantics it looks like everytime I write move ctor or operator= I have to explicitly state in each of them (code repetition/error introduction) how to nullify moved object which isn't the best possible option I think. Looking forward to your opinions on this subject.
views:
152answers:
3What's wrong with this:
struct Object
{
Object(Object&&o) { ...move stuff...; nullify_object(o); }
Object & operator = (Object && o) { ...; nullify_object(o); }
void nullify_object(Object && o);
};
Or the alternative of having nullify_object called on the target: o.nullify();
I see no major benefit of adding YANLF.
The move constructor/assignment is where you steal the resources and leave the state of the stolen object's resources in a state that enables safe destruction of the object when its destructor is called. You can't see nor access the temporary "value" from which you steal the resources except in the move constructor/assignment.
As an example, let's take a string. That's where you would steal the allocated resources and size from the temporary object and you would set its values to your own values (which should be null and 0 if your object was default constructed).
Could you bring a concrete example, where it would be useful. E.g, as far as I understand, move assignment might in the general case be implemented as
this->swap(rhv);
The swap method is probably beneficial in any case, if the class benefits from move semantics. This nicely delegates the work of releasing the old resources of *this
to the regular destructor.
Without particular examples that show a new kind of destructor is an elegant way of achieving correct code, your proposal doesn't look very appealing.
Also, according to the latest revisions, move constructor / assignment operator can be defaulted. This means that very likely my classes will look like this:
class X
{
well_behaved_raii_objects;
public:
X(X&& ) = default;
X& operator=(X&&) = default;
};
No destructor at all! What would make it appealing to me to have two destructors instead?
Also take into account that the assignment operator has old resources to deal with. As per current standard you have to be careful that normal destructor call is fine both after construction and assignment, and IMO, similarly with the proposed move destructor you would have to take care in the constructor and the assignment operator that the same move destructor can be safely called. Or would you like two move destructors - one for each? :)
Reworked example of the msdn example in the comments with move constructor/assignment
#include <algorithm>
class MemoryBlock
{
public:
// Simple constructor that initializes the resource.
explicit MemoryBlock(size_t length)
: length(length)
, data(new int[length])
{
}
// Destructor.
~MemoryBlock()
{
delete[] data; //checking for NULL is NOT necessary
}
// Copy constructor.
MemoryBlock(const MemoryBlock& other)
: length(other.length)
, data(new int[other.length])
{
std::copy(other.data, other.data + length, data);
}
// Copy assignment operator (replaced with copy and swap idiom)
MemoryBlock& operator=(MemoryBlock other) //1. copy resource
{
swap(other); //2. swap internals with the copy
return *this; //3. the copy's destructor releases our old resources
}
//Move constructor
//NB! C++0x also allows delegating constructors
//alternative implementation:
//delegate initialization to default constructor (if we had one), then swap with argument
MemoryBlock(MemoryBlock&& other)
: length(other.length)
, data(other.data)
{
other.data = 0; //now other can be safely destroyed
other.length = 0; //not really necessary, but let's be nice
}
MemoryBlock& operator=(MemoryBlock&& rhv)
{
swap(rhv);
//rhv now contains previous contents of *this, but we don't care:
//move assignment is supposed to "ruin" the right hand value anyway
//it doesn't matter how it is "ruined", as long as it is in a valid state
//not sure if self-assignment can happen here: if it turns out to be possible
//a check might be necessary, or a different idiom (move-and-swap?!)
return *this;
}
// Retrieves the length of the data resource.
size_t Length() const
{
return length;
}
//added swap method (used for assignment, but recommended for such classes anyway)
void swap(MemoryBlock& other) throw () //swapping a pointer and an int doesn't fail
{
std::swap(data, other.data);
std::swap(length, other.length);
}
private:
size_t length; // The length of the resource.
int* data; // The resource.
};
Some comments on the original MSDN sample:
1) checking for NULL before delete
is unnecessary (perhaps it is done here for the output which I have stripped, perhaps it indicates a misunderstanding)
2) deleting resources in the assignment operator: code reduplication. With the copy-and-swap idiom deleting previously held resources is delegated to the destructor.
3) copy-and-swap idiom also makes self-assignment checks unnecessary. It is not a problem if the resource is copied before it is deleted. - ("Copy the resource regardless" on the other hand only hurts when you expect lots of self-assignments done with this class.)
4) Assignment operator in the MSDN's example lacks any kind of exception safety: if allocating new storage fails, the class is left in an invalid state with an invalid pointer. Upon destruction undefined behavior will happen.
This could be improved by carefully reordering the statements, and setting the deleted pointer to NULL in-between (unfortunately it seems that the invariant of this particular class is that it always holds a resource, so having it cleanly lose the resource in case of an exception isn't perfect either). By contrast, with copy-and-swap, if an exception happens, left-hand value remains in its original state (much better, operation can't be completed but data loss is avoidable).
5) The self-assignment check looks particularly questionable in the move assignment operator. I don't see how left-hand-value could be the same as the right-hand-value in the first place. Would it take a = std::move(a);
to achieve identity (looks like it would be undefined behavior anyway?)?
6) Again, move assignment is unnecessarily managing resources, which my version simply delegates to the regular destructor.
Conclusion: the code reduplication you are seeing is avoidable, it is only introduced by a naive implementation (one which you, for some reason, tend to see in tutorials, probably because code with reduplications is easier to follow for learners).
To prevent resource leaks, always free resources (such as memory, file handles, and sockets) in the move assignment operator.
... if code reduplication is fine by you, otherwise reuse the destructor.
To prevent the unrecoverable destruction of resources, properly handle self-assignment in the move assignment operator.
... or make sure you never delete anything before you are sure you can replace it. Or rather a SO question: is it possible for self-assignment to happen in case of move assignment in a well-defined program.
Furthermore, from my draft (3092) I find that if a class has no user-defined copy constructor / assignment operator and nothing prevents the existence of a move-constructor / assignment, one will be declared implicitly as defaulted. If I'm not mistaken, this means: if the members are things like strings, vector, shared_ptrs etc, in which case you normally wouldn't write a copy constructor / assignment, you'll get a move constructor / move assignment for free.