Boost logo

Boost :

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


Howard Hinnant wrote:
>
> On Apr 16, 2007, at 4:32 PM, Ion Gaztañaga wrote:
>
>> I'm terrible explaining my points.
>
> I can relate. :-)

I'll try harder now ;-)

> There exists a small gap between what was voted into the WP last week
> regarding move semantics, and what my intent is. Below I attempt to
> explain my intent, and where that differs from what is in the WP, I
> will attempt to fix with defect reports.

I haven't cached the differences between your intention and the WP. Are
valid assignment/move-assignment for moved values required for types to
be inserted in std containers?

> [snip moving A to a container...]
>
> The definition of "valid" is up to the author of A. At a minimum,
> generic code will need to destruct and/or assign "a" a new value.
> However the author of A is free to define as part of the class
> invariant:
>
> After an A is moved from, you can't call A::bar()
>
> I don't think that is a very good design myself. But I also don't
> think the standard should prohibit it.

I've been revising some classes which implement pseudo-move semantics in
Interprocess and for unique resource owning classes (a shared memory
object descriptor which is not copyable but movable) A::bar() would
return error instead of crashing. Not a big advantage over crashing, but
otherwise there is nothing to do, because the resource is unique. And
these classes have the interesting "trivial destructor after move"
trait, something we could not achieve with objects that still hold
resources. It's a shame that the compiler can't automatically detect
such classes to avoid calling destructors if they've been moved.
std::vector would really appreciate it... ;-)

> Originally I preferred vector move assignment as just swap(). However
> I've recently become convinced that this is not the best definition.
> I now prefer the semantics of clear(); swap();. This means that move
> assignment is O(N) instead of O(1). Ok Ion, take a deep breath and
> stay with me for just a minute longer. :-)

Holding my breath...

> The semantics of ~thread() is going to be cancel(); detach();. That
> is, if thread A is holding a std::thread referencing thread B, and
> thread C cancels thread A, as thread A's stack unwinds, it will
> execute b.~thread() which cancels B, but does not wait around for B to
> respond to the cancel. Thus canceling A will not hang waiting on B to
> finish up.
>
> [more good stuff]

I've been revising a bit the implementation of move semantics for
Intrusive containers and I've also seen that there is no advantage if we
are constructing a new value. However, my hope is that move semantics
will be very successful and people will start using move semantics
everywhere:

class ObjectWithString
{
   public:
   ///..
   //We can assign from anything convertible to std::string
   template<class T>
   set_str(T &&convertible_to_str)
   { str_ = forward(str); }

   private:
   std::string str_;
}

ObjectWithString object_with_string;
std::vector<ObjectWithString> object_vect;

for(int i = 0; i < NUM; ++i){
   str += "prefix_";
   str.append(to_string(i));
   str_vect[i].set_str(move(str));
}

If target memory is deallocated with every move-assignment the
complexity might be good in theory but the result is "my code calls
delete/new every two steps". If strings are passed as lvalues, and the
target has capacity, the copy assignment might be more efficient than
the move assignment, because no memory is being deallocated/allocated
(str is being reused in the loop).

On the other hand, if memory is swapped to the outside world, we can
avoid a lot of allocations, because str can reuse memory already present
in the contained objects. Note that this is compatible with your clear()
+ swap() approach. If the standard does not guarantee this, my only hope
is to use move construction hoping that the implementation optimizes
this (although I bet that most implementation will swap the memory,
specially if that's "extra officially" encouraged).

> [vector thread example...]

I've just realized that I can end criticizing arguments that I've
passionately defended before. In the GC debate I defended
the need of deterministic destructors, something that I miss defending
swap() as the implementation of the move assignment, because nobody
really knows when the moved source is going to be destroyed. Your
clear() + swap() proposal seems the answer.

However, if the object to be move assigned is std::vector<std::string>,
the call to destructors will deallocate all the strings. Maybe the
implementations could just detect that vector is a container holding a
value whose destructor has no "visible" side effects (well, deallocating
memory: it's holding a container of values with trivial destructors). So
move assignment for this:

std::vector<std::vector<std::string> >

can be quite heavy unless the implementation can detect that there is no
need to call destructors. I imagine that unless the loop
I've written or similar, the rvalue is going to be destroyed anyway, so
  maybe there is no big impact. Time will tell.

> Getting back to shared_ptr move assignment, I would like to see the
> target's reference count decremented if it is not already at 0 prior
> to the move assignment (just as in copy assignment - this is the
> "clear" part of the algorithm). I would also like to not see any
> *new* constraints placed on the source as a result of being moved
> from. And finally I do not see the need to specify the value of the
> source after the move. I would like vendors to have the freedom to
> implement shared_ptr move assignment exactly as they do shared_ptr
> copy assignment. If atomic operations can be avoided (for example) by
> assuming that the source is a temporary (under move assignment) then
> so much the better.

shared_ptr can also optimize the trivial destructor case (or even
std::vector<std::string> > case) but apart from avoiding the atomic
operations it can also swap the heap allocated reference count (what
happens with the deleter?) hoping someone can reuse it outside. After
all, shared_ptr looks like a (special) container.

Still breathless,

Ion


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