Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2002-02-06 16:47:28


On Wednesday, February 6, 2002, at 10:37 AM, Hamish Mackenzie wrote:

> I have uploaded move.tar.gz which implements some of these ideas.
> http://groups.yahoo.com/group/boost/files/move.tar.gz
> I have not had time to test it properly but let me know what you think.

I've taken a quick look at this. I need more time to study it. But
first impressions are pretty good. One thing that caught my attention
pretty quickly was several similarities to John Maddock's design that I
noted at the beginning of this thread (and I think this is a good thing):

On Monday, February 4, 2002, at 04:26 PM, Howard Hinnant wrote:

> John Maddock has suggested:
>
> T b = std::move(a);

On Monday, February 4, 2002, at 07:40 PM, Howard Hinnant wrote:

> John Maddock has suggested that std::move return the struct
> std::move_t<T>. Then the class T could create a constructor and
> assignment operator that accepted a std::move_t<T>. This has a nice
> parallel with the current requirements of implementing copy semantics.

Your move_from is quite similar to John's move, and your move_source<T>
is quite similar to John's move_t<T>.

> One thing that is probably important is adding overloads that take the
> allocator to use as an argument
>
> template< typename T, class Allocator >
> inline move_construct( T &, Allocator a );
>
> etc.

I don't see a need for this. If a class needs an allocator it should
take it from the source.

> But would you agree that swap is the fastest way to move existing
> containers such as std::vector and std::string (ie. without changing
> them)?
>
> Perhaps we could make the default to use copy construction and
> assignment and specialize to use swap for containers?

I think I see where are viewpoints are fundamentally different. And
that is not a particularly bad thing. It is just helpful to recognize:

You are concerned with getting move semantics up and running today, with
today's compilers and libraries. And that is a laudable goal.

I am more concerned with designing move semantics from the ground up for
inclusion into the next C++ standard. And all parts of the puzzle are
open to change, including the language, and the std::lib. Backwards
compatibility is only important so far as ensuring that existing code
continues to have the same semantics and performs no worse than it does
today. Since move sematics do not exist today (except for auto_ptr of
course), we do not have to get existing classes moving. Otoh, if we can
get existing classes moving for free, then why not take advantage of
that? I'm still undecided on this issue.

Yes, the /only/ way to move existing std::containers is with swap (or
maybe clear/splice with list). However I see no way to have vector or
deque take advantage of this new facility without changing their
implementation. Since they have to change anyway to use move, I'm not
overly concerned at this point that they might have to change to be
moved.

> I feel swap has at least two advantages though
> 1) People who have read Herb's "Exceptional C++" already know the value
> of swap so it exists (or should) for most complex objects.
> 2) It can provide no-throw garantee easily

No arguments here. I would like to add move to the toolbox so that we
can say the same things about it.

> I think the class designer should have the choice of using swap because
> it is simpler (in my opinion). The interation with the destructor is
> hard to get wrong. Consider
>
> class x
> {
> int ptr_;
> public:
> x() : ptr_( 0 ) {}
> ~x() { if( ptr_ ) delete ptr_; }
>
> ...
>
> x( move_source< x > src ) : ptr( src.get().ptr_ ) {}
> };
>
> Failing to set src.get().ptr_ = 0 in the move constructor is a design
> error that is hard to make if eaverything is done with swap.

Agreed. Using swap as an implementation detail of move should not be
barred from the author of x.

>> 2. How does one detect if an object implements move semantics (in
>> templated code)?
> We could add something to move_traits< T > to indicate how move will
> behave. It could indicate if move is no-throw. Unfortunately it will
> require the class designer to provide the information in some way.

Yup and Yup. I dislike a facility that requires class authors to
register with it, except for the case where the number of objects that
need to register is known and manageable. For example I can deal with
having to register all scalars with the move facility. But I dislike
requiring Joe Programmer to register MyClass with it. It is a source of
error that could all to easily go undetected (detected only by
performance monitoring or counting copy constructions for example).

John Maddock suggested a traits class that would be specialized for
scalars (actually I think for PODS if is_POD comes on line), but for
classes it would use

        is_convertible<move_source<x>, x>::value

(of course he used move_t, not move_source, but they are the same idea)

This clever technique means that Joe Programmer only needs to provide
the converting contructor taking move_source<x>, and then x will be
automatically "registered" with move.

Unfortunately this will only detect the constructor taking move_source.
It won't detect move semantics if they are somehow provided "for free",
except perhaps for PODS. And it won't detect move assignment no matter
what. The best I currently have for that problem is a rule: If you
provide move construction, you must also provide move assignment (and
vice-versa).

>> 3. How does one move from a temporary?
> Hmm...
>
> T f();
> T dest;
>
> move_to( dest ) = mover_from( f() );
>
> Will fail to compile (for good reason). But compilers should do a good
> job with return values so
>
> T temp( f() );
> move_to( dest ) = move_from( temp );
>
> Should be just as fast.

I could be wrong, but I think people will complain. auto_ptr went to
great lengths to insure that you could move from a temporary, but not
from a const. Indeed, auto_ptr_ref looks remarkably similar to your
move_source<T>. I don't have the answer for this one. I wish I did.
Imo, this is the hardest part of move, and may require help from the
language.

I'm still getting my head wrapped around move_construct. If I'm
understanding it correctly I'm not sure I like the ease of use factor:

void foo(T& source)
{
        // aligned_buf somehow defined:
        T& t = *(T*)aligned_buf;
        move_construct(&t, source);
}

I would much rather see:

void foo(T& source)
{
        T t(move_from(source));
}

Or perhaps just:

void foo(T& source)
{
        T t(move(source));
}

Since the author of T may be required to provide the move_source
constructor anyway (say to avoid the default constructor requirement), I
am very inclined to /require/ the move_source constructor (except for
PODS). That would clean up the end user syntax considerably.

-Howard


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