views:

152

answers:

3

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.

A: 

What'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.

Noah Roberts
@Noah yes but in your case you have to remember to invoke nullify_object. In my proposition move destructor would be invoked implicitly.
There is nothing we can do
@Noah I would be appreciated if you would clarify what YANLF means.
There is nothing we can do
yet another new language feature
Noah Roberts
@Noah And what's wrong with new language feature which will make our life easier, and our code safer?
There is nothing we can do
Nothing. But there comes a point when a language designer or committee must stop adding features and just release the language so people can implement it. Since your proposed feature adds very, very little in the way of making life easier or safer, it would be really silly to just stick it on to the long list of stuff to work into the language. At some point it becomes the responsibility of the developer to make sure they call the functions they need to in order to create the behavior they want.
Noah Roberts
@Noah I'm sure people where saying similar things when dtor where proposed. And as I agree that at some point committee must stop adding features to the __current__ standard.
There is nothing we can do
continue But also it is more than certain that there __will be another standard__ and another features will be added. I'm sure that moving dtor would be more than welcome by programmers if it would be added. Wouldn't you preffer write your code in just one place and be sure that this code will be executed everytime is needed or you rather preffer write this same code many times and you preffer to personnaly make sure that certain function is invoked everytime is needed?
There is nothing we can do
Frankly, since I make regular use of RAII I've not seen any need to do anything extra that might go into a "move destructor" the few times I've implemented move functionality.
Noah Roberts
@Noah so what you're saying in your move ctor optor= you didn't do any extra code to "clean" used object? Just RAII was enough?
There is nothing we can do
A: 

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).

David
@David can't agree with you for the reason that if it's you who designs the class then you can see and you can access any value from your class. As for your string example you don't know how designers of this class deal with moving semantics but they dealt with this and they saw every bit of this class.
There is nothing we can do
A: 

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.

UncleBens
@Uncle could you please attach some link so I can read about it? I'm really interested in it. Does it mean that according to what you're saying there won't be need for dtor anymore?
There is nothing we can do
You need a link that says `class X {char* name; ... };` needs a destructor, whereas `class Y {string name; ...};` doesn't?
UncleBens
@UncleBens no I asked for a link to a material in which would be explained how default applied to cpy ctor or assignment optor would work. Having said that, what you've said in your example means that of course you do not need an moving dtor only if you are not using resources "directly". In any other case it would be useful to have _move destructor_ just like is useful to have dtor.
There is nothing we can do
According to my version of the draft, the default version will simply perform member-wise moves (like normal copying performs member-wise copying). - As to cases where resources are used "directly", your suggestion still needs a "motivating example". Right now you are saying "it just would be useful", and I say "it isn't hard to imagine simple and common idioms that don't require any new destructor, just like the copy-swap idiom in current C++".
UncleBens
@UncleBens isn't that motivating enough that you have to write your code in just one place (moving dtor) and you do not have to worry about invoking it explicitly every time move semantics are taking place? I'm sorry but those are almost exactly the same reasons why we have "regular" dtor. Or are you saying that having a "regular" dtor is also something you can easily live without?
There is nothing we can do
@A-ha: And IMO a regular dtor is enough. If a buffer needs deleting, `delete [] buffer;` needs to be called. Why would I need two destructors for this? For example, if I wrote a String class, would I benefit from your suggestion and how?
UncleBens
@UncleBens Of course you would benefit, for the simple reasons I've mentioned in my last comment. Isn't that enough to have safe code?
There is nothing we can do
@A-ha: Without an example, I fail to get that argument. To me it seems to be suggesting that virtually the same code should be written in two places (two destructors). A resource needs to be released (or not - deallocation functions normally can handle NULL). Why would I need two different destructors for that? What would a "moving destructor" for a String do?
UncleBens
@UncleBens and could you provide sample (but fully workable) of moving ctor of your string? If for example in your moving ctor you are just "emptying" or "cleaning" your object from which resources are moved from but not destroying it then moving dtor would be used for those operations. Note that there is no "destruction" because no real dtor is used. It's just preparing the object to be in a state which will be correct and won't cause errors when this object will get "destroyed" by "real" dtor. Just like in my question to which I've attached link.
There is nothing we can do
continue in this question. There in order to "prepare" object for being destroyed I have to "nullyfy" it by resetting all its fields, and that's the job for moving dtor. Never the same code is written. I will say even more: Code is only written once for each task and you do not have to worry that you'll forget to invoke it.
There is nothing we can do
//In order to see this code better check the link in my questionMemoryBlock(MemoryBlock _length = other._length; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. //The code below could be and IMO should be in moving dtor other._data = NULL; other._length = 0; }
There is nothing we can do
continue here is the link to original article: http://msdn.microsoft.com/en-us/library/dd293665.aspx
There is nothing we can do
continue and thats the line from this article: 3.Assign the data members of the source object to default values. This prevents the destructor from freeing resources (such as memory) multiple times. I just want to point your attention to the sentence: This prevents the destructor from freeing resources (such as memory) multiple times. And this is for me job for moving dtor. Instead of doing this manually every time, you write this code in one place and you do not worry about invoking it. Does it make sense to you or still not?
There is nothing we can do
continue P.S. Going to the bed so if I'll get answer from you I will reply to it tommorow. Good night.
There is nothing we can do
@A-ha: Updated answer with an analysis of the MSDN example. Conclusion: any reduplication is there because it was put there. Reduplication is avoidable with different techniques.
UncleBens
@UncleBens thank you for your answer. It did clarify little bit in my head this whole concept of moving this and moving that. I wonder why for God's sake in MSDN they show "incorrectly" implemented example? If you try to teach someone do not show him wrong ways in hope that there are easier to understand. Seriously.
There is nothing we can do
@A-ha: A reason why tutorials generally implement assignment like this might be that it makes all the steps explicit, at the expense of code reduplication. With the copy-n-swap idiom all the steps are more-or-less implicit, and for a beginner it might be hard to realize what is going on. - This, and may-be some tutorials are "from beginners to beginners".
UncleBens
@UncleBens I think if that's the true what're saying then they should at least indicate on which level is this tutorial. Otherwise if I (any user) read from their website I'm taking this as the most appropriate way and it looks like it isn't and it's merely tutorial for novices. How on earth am I going to learn proper technics if on website like this they showing stuff for novices without even saying so. Having that said I want to thank you once more for your help. And by the way Microsoft pisses me off more and more recently.
There is nothing we can do