Boost logo

Boost :

From: Ion Gaztañaga (igaztanaga_at_[hidden])
Date: 2007-04-16 16:24:33


Ion Gaztañaga wrote:
> Since current move semantics are non-destructive the state of the object
> should be usable. For objects that have a default-constructor (like
> shared_ptr or vector) the moved state could be equal to
> default-constructed (which is consistent *and* usable).

I want to correct this. Default-constructed "could" be ok, but no
optimal. For example:

std::vector<T> myvect(...);
std::vector<T> myvect2(std::move(myvect));

The move operation could be implemented as "swap" so myvect could hold
memory previously hold by myvect. This opens the possibility to reuse
existing resources. Take for example the implementation of vector with
move semantics:

This is the old buffer

class vector
{
   // ...
   T *buf_;
   size_type length_;
   size_type capacity_;
};

Let's insert an object in the beginning. The vector has enough capacity
we just need to move the objects and assign the new one in the first
slot. Now if moved objects can be reused (code not compiled and it might
have flaws).

//Suppose length_ > 0...

if(length_ > 0){

allocator.construct(buf_+length_, move(buf_[length-1]));

++length_;

//Move values one position
//Reuse of stl algorithms with a simple iterator wrapper
//that moves objects. Don't worry about the moved
//value since it can be reused
std::copy_backwards
   ( move_iterator(buf_)
   , move_iterator(buf_ + length_ - 2)
   , move_iterator(buf_ + 1));

buf[0] = value;
//or if value was catched as a rvalue reference
buf[0] = move(value);

}
else{

allocator.construct(buf_, move(value));
++length_;

}

move_iterator is described here:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1771.html,
search "24 - Iterators library" to find it.

Simple, efficient and easy. And exception safe (when coded correctly,
which might not be my case ;-) ). If moved objects can't be reused, want
can I do with buf[0]? Should I start doing placement destruction, and do
a placement new after that? What if the second step throws? God!

So one important point is that reusing objects leads to easier code in
many cases. Transition from a usual std::vector to a move-capable vector
is really easy if we have an iterator move-wrapper (whose operator *()
returns a rvalue-reference instead of an lvalue one) and if moved
objects can be reused. The implementation just needs to wrap the copy
operations between objects that were already in the container with
move_iterator.

The same happens if we have two arrays of strings in our application

std::string str_array[SIZE];

//.. strings are filled

std::string str_other_strings[SIZE * 2];

std::copy(move_iterator(&str_array[0]
          ,move_iterator(&str_array[SIZE]
          ,move_iterator(&str_other_strings[0]);

//if move is implemented as swap(), str_array
//strings still have memory

//other operations...

//This can lead to ZERO allocations if previous
//strings had memory taken from the move-target
for(int i = 0; i < SIZE; ++i){
   str_array[0] += "prefix_";
   str_array[0] += boost::lexical_cast(i);
}

So my points are:

* reusing moved objects leads to easier code.
* reusing moved objects might improve performance.
* reusing has no impact with temporaries because they
   are going to be destroyed anyway.
* the user can explicitly request resource liberation for
   moved objects or destroy them if he wants to be sure
   that resources were liberated and about bloat (it has no
   guarantee if the state of the moved objects is something like
   "destructor will be safely called").

Regards,

Ion


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk