f



Move semantics and moved/empty objects

Following a discussion I had on the boost-devel mailing list [1] about
objects that can never be empty/null, I started thinking about issues
that move semantics introduce.

[1] http://article.gmane.org/gmane.comp.lib.boost.devel/177969

Indeed, when moving an object, it is required to leave the object in a
state which is at least valid enough to call its destructor on.

In the following, let's only consider objects which own resources that
must be freed in the destructor.

The moved object is then left in an empty state, which might
contradict the invariants of the type. However, it could be argued
that the empty state could only be reachable from a move, hence still
allowing types that can never be empty to exist and to make use of
move semantics.

The question is: what should be the allowed operations on a moved/
empty object?
- Destruction of course is necessary. That basically means that
destructing an object will always require a conditional branch or an
indirection.

If you only consider that you move from rvalues and nothing else, I
suppose that's enough.

But what if you want to use std::move to express algorithms, such as
erase, as demonstrated by Steven Watanabe?
- Allowing empty objects to be "replaced" with non-empty ones. That is
to say allowing operator= to function with the left operand being an
empty object. That means an indirection in operator=.
- Allowing empty objects to be assigned to other objects and to be
constructed from. That means a double indirection in operator=, one in
the copy constructor, and emptiness can propagate. That doesn't sound
very desirable, it's as if you cannot rely on the invariant at all.

std::swap actually makes use of operator= with an empty left operand.
So I suppose that at least needs to be allowed, and I guess it's not
so bad since at least it doesn't propagate, it's just a little
performance penalty. Note that this was also required be the "erase"
algorithm as described by Steven Watanabe.

Ideally I would personally prefer to restrict to destructor-only. It
adds a little performance penalty which can be avoided altogether. I'm
in a favor of only using destruct/reconstruct on empty types to
perform assignment. Yes, it isn't exception-safe, but restoring an
empty state in case of failure should be nothrow. That would of course
require the addition of another construction primitive.

I would like the standard committee to clearly define guidelines as to
what types ought to do when faced with move semantics, what operations
should still say valid, especially types that aim to provide a never-
empty invariant.
It would really be a shame if C++0x couldn't allow those types, which
I consider much superior in safety and elegance, to make use of its
main new feature in the typing system.

As a side note, I'm not sure the fact that lvalues can bind to rvalue
references is really a good idea. The type system cannot protect the
user into moving from lvalues and accessing them later, which would
inherently crash or worse. That kind of thing should require casting
IMHO.

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Mathias
7/29/2008 9:58:19 AM
comp.lang.c++.moderated 10738 articles. 1 followers. allnor (8509) is leader. Post Follow

16 Replies
865 Views

Similar Articles

[PageSpeed] 21

On Jul 29, 12:58 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
....
> Ideally I would personally prefer to restrict to destructor-only. It
> adds a little performance penalty which can be avoided altogether. I'm
> in a favor of only using destruct/reconstruct on empty types to
> perform assignment. Yes, it isn't exception-safe, but restoring an
> empty state in case of failure should be nothrow. That would of course
> require the addition of another construction primitive.

This sounds a little like what I refer to as "destructive move
semantics".  You might be interested in this rather dated link which
discusses that approach:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Alternative%20move%20designs

> I would like the standard committee to clearly define guidelines as to
> what types ought to do when faced with move semantics, what operations
> should still say valid, especially types that aim to provide a never-
> empty invariant.

That is going to depend on what algorithm the type is being used in.
For example the current WP says this about swap:

> Requires: Type T shall be MoveConstructible (33) and MoveAssignable (35).

Thus, a "moved-from" object can expect to be move-assigned into.  Also
there is a blanket Destructible statement somewhere in 17 I believe.
So a moved-from type can be expect to be destructed.  swap will do
nothing else with an object (it is not permitted to).

> As a side note, I'm not sure the fact that lvalues can bind to rvalue
> references is really a good idea. The type system cannot protect the
> user into moving from lvalues and accessing them later, which would
> inherently crash or worse. That kind of thing should require casting
> IMHO.

See this link:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html#Use%20of%20Rvalue%20Streams

for an application where letting lvalues bind to rvalue references
produces a better environment for the client. In a nutshell, there are
applications where you want to accept lvalues or rvalues as non-const,
and modify the argument, and not care whether the argument is an
lvalue or rvalue.  It is only when you want to treat lvalues and
rvalues differently that you need to overload on this property.

Only allow lvalues:
     void foo(A&);

Only allow rvalues (rare):
     void foo(A&) = delete;
     void foo(A&&);

Allow both lvalue and rvalue and treat each differently:
     void foo(A&);
     void foo(A&&);

Allow both lvalue and rvalue and treat each the same:
     void foo(A&&);

If lvalues can be const, this changes to:

Disallow non-const rvalues:
     void foo(const A&);
     void foo(A&&) = delete;

Only allow (non-const) rvalues:
     void foo(const A&) = delete;
     void foo(A&&);

Allow both lvalue and rvalue and treat non-const rvalues differently
     void foo(const A&);
     void foo(A&&);

Allow both const lvalues and rvalues and treat each the same:
     void foo(const A&);

-Howard


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Howard
7/29/2008 4:45:46 PM
on Tue Jul 29 2008, Mathias Gaunard <loufoque-AT-gmail.com> wrote:

> Following a discussion I had on the boost-devel mailing list [1] about
> objects that can never be empty/null, I started thinking about issues
> that move semantics introduce.
>
> [1] http://article.gmane.org/gmane.comp.lib.boost.devel/177969
>
> Indeed, when moving an object, it is required to leave the object in a
> state which is at least valid enough to call its destructor on.
>
> In the following, let's only consider objects which own resources that
> must be freed in the destructor.
>
> The moved object is then left in an empty state, which might
> contradict the invariants of the type. 

Then someone documented the wrong invariant.  The invariants are
invariants, so they should include whatever moved-from state is
required.  Anything else leads to confusion at best.

> However, it could be argued that the empty state could only be
> reachable from a move, hence still allowing types that can never be
> empty 

except when moved-from, I suppose.

> to exist and to make use of move semantics.
>
> The question is: what should be the allowed operations on a moved/
> empty object?
> - Destruction of course is necessary. That basically means that
> destructing an object will always require a conditional branch or an
> indirection.
>
> If you only consider that you move from rvalues and nothing else, I
> suppose that's enough.
>
> But what if you want to use std::move to express algorithms, such as
> erase, as demonstrated by Steven Watanabe?

Then you probably need to allow assignment to a moved-from object.

> - Allowing empty objects to be "replaced" with non-empty ones. That is
> to say allowing operator= to function with the left operand being an
> empty object. That means an indirection in operator=.

I don't know what you mean by indirection.

> - Allowing empty objects to be assigned to other objects and to be
> constructed from. That means a double indirection in operator=, one in
> the copy constructor, and emptiness can propagate. That doesn't sound
> very desirable, it's as if you cannot rely on the invariant at all.

I don't know what you mean by any of that bullet.  Could you please
explain?

> std::swap actually makes use of operator= with an empty left operand.
> So I suppose that at least needs to be allowed, 

By std::swap.

> and I guess it's not so bad since at least it doesn't propagate, it's
> just a little performance penalty.

What performance penalty?

> Note that this was also required be the "erase" algorithm as described
> by Steven Watanabe.
>
> Ideally I would personally prefer to restrict to destructor-only. It
> adds a little performance penalty which can be avoided altogether. I'm
> in a favor of only using destruct/reconstruct on empty types to
> perform assignment. Yes, it isn't exception-safe, but restoring an
> empty state in case of failure should be nothrow. 

I don't understand what you're driving at here either.  Could you please
spell it out for me?

> That would of course
> require the addition of another construction primitive.

Nor here.

> I would like the standard committee to clearly define guidelines as to
> what types ought to do when faced with move semantics, what operations
> should still say valid, especially types that aim to provide a never-
> empty invariant.

But that's not the committee's job, so don't hold your breath.

Basically, if you want to provide a never-empty invariant, you have to
figure out how to do it so that moved-from objects are not empty, for
whatever your definition of "empty" is.

> It would really be a shame if C++0x couldn't allow those types, which
> I consider much superior in safety and elegance, to make use of its
> main new feature in the typing system.

I tend to agree that it's nice to be able to define such a type, but it
turns out to be rare that providing a moved-from state within the bounds
of the invariant you'd define anyway is either inconvenient or
expensive.  When it is, you have to decide whether you want the
optimizations of move semantics or you want the stricter invariant.

> As a side note, I'm not sure the fact that lvalues can bind to rvalue
> references is really a good idea. The type system cannot protect the
> user into moving from lvalues and accessing them later, which would
> inherently crash or worse. That kind of thing should require casting
> IMHO.

We've been over this in the committee too.  It introduces a hole in the
type system that I'd rather plug.

-- 
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
David
7/29/2008 10:40:27 PM
On 30 juil, 01:45, Howard Hinnant <howard.hinn...@gmail.com> wrote:

> That is going to depend on what algorithm the type is being used in.
> For example the current WP says this about swap:
>
> > Requires: Type T shall be MoveConstructible (33) and MoveAssignable (35).
>
> Thus, a "moved-from" object can expect to be move-assigned into.  Also
> there is a blanket Destructible statement somewhere in 17 I believe.
> So a moved-from type can be expect to be destructed.  swap will do
> nothing else with an object (it is not permitted to).

I hardly see how those concepts require that.
They require
T obj(rv);
and
lv = rv;
to be valid operations, that is all.
(lv and rv being respectively lvalue and rvalue of type T)

An lvalue shouldn't be in a moved-from state, since moved-from state
is only required to be reachable from rvalues.

If allowing moved-from objects to be assigned-to is the expected
behaviour, it should require
T obj(static_cast<T&&>(lv));
and
lv = rv;
to be valid instead.

Also see my response to David Abrahams in this thread for similar
thoughts.


> See this link:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html#Us...
>
> for an application where letting lvalues bind to rvalue references
> produces a better environment for the client. In a nutshell, there are
> applications where you want to accept lvalues or rvalues as non-const,
> and modify the argument, and not care whether the argument is an
> lvalue or rvalue.  It is only when you want to treat lvalues and
> rvalues differently that you need to overload on this property.

I know, however it leads to problems.

After decay from lvalue to rvalue-reference, the information is lost:

void foo(T&& o) // I want to work with a T, potentially modifying it
and I don't care whether it's an lvalue or an rvalue
{
     o.value = 42; // we modify the object
     some_container c;
     c.insert(o); // I put my object in my container -- actually
potentially wrong!
}

Here, the container will treat 'o' as an rvalue even if it was indeed
an lvalue.
The fix is to use std::forward<T>(o) instead of 'o'. That doesn't
sound like a very good usage pattern to me, since in the case where
you don't want to distinguish between lvalue and rvalue, you would
want to use std::forward for every single access to 'o', and failure
to do so should be a grave programming error.

I propose that another syntax be introduced for mutable references to
lvalues or rvalues.
Why not void foo(mutable T& o), which would clearly be the opposite of
void foo(const T& o)?

Also, another problems with rvalue references is that they are
obviously unsafe constructs, since an rvalue is given a name, can be
accessed multiple times and may even end up as being in an invalid
"moved-from" state anytime.

For example

void foo(const T&) = delete;
void foo(T&& o) // I know o is really an rvalue
{
     some_container c;
     c.insert(o); // move my object into the container

     /* Now I shouldn't touch 'o' anymore, but the type system doesn't
prevent that! */
     c.insert(o); // move my object again, despite it being invalid --
may break
}

So I think that ideally, in addition to only rvalues binding to rvalue
references, compilers should perform analysis to make sure rvalue
references were only accessed once. (Also, accessing only part of the
object doesn't prevent accessing the other part of the object, but
prevents accessing the whole object)

A little crazy thought: if we had really separate types for rvalues, I
suppose we could also eventually make a change of ABI to fix the case
where it is needed to check in the destructor whether the object is
empty or not.
Indeed, if functions taking an rvalue reference were responsible for
object destruction, they could simply forward that responsibility to
any other deeper such function, or call the destructor when no deeper
such function exists.


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Mathias
7/30/2008 1:58:19 PM
On 30 juil, 07:40, David Abrahams <d...@boostpro.com> wrote:

> I don't know what you mean by indirection.

A conditional branch, a call through a function pointer, whatever that
allows making the two cases distinct.


> > - Allowing empty objects to be assigned to other objects and to be
> > constructed from. That means a double indirection in operator=, one in
> > the copy constructor, and emptiness can propagate. That doesn't sound
> > very desirable, it's as if you cannot rely on the invariant at all.
>
> I don't know what you mean by any of that bullet.  Could you please
> explain?

Let's take the example of a resource. I will first suppose that the
object is never empty.
The resource is identified by a pointer, will several constructs :
open (throws if fails), close (never throws), and copy (throws if
fails).
close and copy only works will valid, open resources.

This example is not unlike the recent N2698 paper, except that I have
added copy support too, and fixed an error in operator= (the paper
called delete when I should have called close).
http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2698.html

struct resource
{
     resource(args...) : h(open(args...)) {}

     resource(const resource& other) : h(copy(other.h)) {}

     resource(resource&& other) : h(other.h)
     {
         other.h = 0; // "moved-from" state
     }

     resource& operator=(const resource& other)
     {
         Handle* new_handle = copy(other.h);
         close(h);
         h = new_handle;
     }

     resource& operator=(resource&& other) // caution: self-assignment
isn't safe
     {
         close(h);
         h = other.h;
         other.h = 0; // "moved-from" state

         return *this;
     }

     ~resource()
     {
         close(h);
     }

private:
     Handle* h;
};


Now, let's make the destructor callable from moved-from objects (first
bullet).
     ~resource()
     {
         if(h)
             close(h);
     }
This adds one indirection.

Now, let's consider making operator= callable from moved-from objects,
since it is required by std::swap. (second bullet)
     resource& operator=(const resource& other)
     {
         Handle* new_handle = copy(other.h);
         if(h)
             close(h);
         h = new_handle;
     }

     resource& operator=(resource&& other)
     {
         if(h)
             close(h);
         h = other.h;
         other.h = 0; // "moved-from" state

         return *this;
     }
This adds one indirection too.

Let's allow the moved-from objects to be assigned and constructed
from. (third bullet)
     resource(const resource& other) : h(other.h ? copy(other.h) : 0)
{}

     resource& operator=(const resource& other)
     {
         Handle* new_handle = other.h ? copy(other.h) : 0;
         if(h)
             close(h);
         h = new_handle;
     }
This adds one additional indirection in operator=(lvalue) and one in
the copy constructor.

I certainly do not allowing moved-from objects to be assigned and
constructed from, which would require all modifications up to the
third bullet, not because of its inefficiency, but because it can
propagate the "moved-from" state, which I consider highly undesirable.
Such a state should never be accessed to begin with in my opinion!

I would quite like the Standard Library to make promises to not
perform certain operations on moved-from objects, that is all.


> > Ideally I would personally prefer to restrict to destructor-only. It
> > adds a little performance penalty which can be avoided altogether. I'm
> > in a favor of only using destruct/reconstruct on empty types to
> > perform assignment. Yes, it isn't exception-safe, but restoring an
> > empty state in case of failure should be nothrow.
>
> I don't understand what you're driving at here either.  Could you please
> spell it out for me?

I believe moved-from objects should not allow anything but
destruction, and thus there should be no need to support that.
Simply because moved-from objects are supposed to be rvalues, and
rvalues cannot be assigned to, for example.

Moved-from objects are objects which were casted to rvalues, and they
should be treated as such.
There is nothing that can be done on rvalues once they've been moved
except destructing them (which is done automatically), so casted
lvalues should, in my opinion, maintain this and thus performing any
other operation should not be done by any part of the standard
library.

However, std::swap does it.

void swap(T& a, T& b)
{
     T tmp(std::move(a));
     a = std::move(b);
     b = std::move(tmp);
}

The problematic line is a = std::move(b).
We lied to T::T(T&&) saying that 'a' was an rvalue where it really
wasn't. We tricked it into believing that. T::T(T&&) could then
theoretically choose to put 'a' in a state it cannot be assigned-to,
since rvalues cannot.
But then, the code would break.

I kind of accuse that swap implementation to be evil.

The alternative solution I propose is to destruct 'a' and construct-
move 'b' in it instead of performing the assignment.


> > That would of course
> > require the addition of another construction primitive.
>
> Nor here.

The aforementioned alternative introduces exception-safety issues.
I proposed to solve them by introducing a nothrow construction
primitive that constructs an object directly in moved-from state.
That's however a fairly bad solution, since I can hardly see how it
could integrate with non move-aware types.


> > I would like the standard committee to clearly define guidelines as to
> > what types ought to do when faced with move semantics, what operations
> > should still say valid, especially types that aim to provide a never-
> > empty invariant.
>
> But that's not the committee's job, so don't hold your breath.

The standard library is expected to heavily make use of movability, be
it for containers of algorithms.
I would like to have good guarantees about what they'll do.

The MoveConstructible and MoveAssignable concepts don't help: they
require the arguments to be (real) rvalues, and there is no problem in
that case.
std::swap is only supposed to require MoveConstructible and
MoveAssignable, but since it lied using casting, it actually passed an
lvalue when an rvalue was expected, which is the real source of the
problem.

*I'm afraid there is really a flaw in the standard here*. So either
the concepts need fixing, or std::swap does.
Two solutions:
- Require bullet 2 for MoveAssignable types
- Really treat lvalues casted to rvalues as rvalues (which seems more
logical to me), that means not accessing them more than once. Think of
alternative techniques to implement std::swap in terms of moves.


> Basically, if you want to provide a never-empty invariant, you have to
> figure out how to do it so that moved-from objects are not empty, for
> whatever your definition of "empty" is.

Why?
Real rvalues can only be accessed once. I can put them in whatever
state I like after that, as far as it is destructible it shouldn't
matter.

It's true though that with rvalue references, I can treat rvalues as
lvalues and access them multiple times. Maybe that is also a defect in
itself however.


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Mathias
7/30/2008 1:58:21 PM
on Wed Jul 30 2008, Mathias Gaunard <loufoque-AT-gmail.com> wrote:

> On 30 juil, 07:40, David Abrahams <d...@boostpro.com> wrote:

<restoring context>
>>> - Allowing empty objects to be "replaced" with non-empty ones. That is
>>> to say allowing operator= to function with the left operand being an
>>> empty object. That means an indirection in operator=.
</restoring context>

>> I don't know what you mean by indirection.
>
> A conditional branch, a call through a function pointer, whatever that
> allows making the two cases distinct.

which two cases?  I'm completely lost as to how indirection is related
to the rest of what you're saying.

>
>> > - Allowing empty objects to be assigned to other objects and to be
>> > constructed from. That means a double indirection in operator=, one in
>> > the copy constructor, and emptiness can propagate. That doesn't sound
>> > very desirable, it's as if you cannot rely on the invariant at all.
>>
>> I don't know what you mean by any of that bullet.  Could you please
>> explain?
>
> Let's take the example of a resource. I will first suppose that the
> object is never empty.
> The resource is identified by a pointer, will several constructs :
> open (throws if fails), close (never throws), and copy (throws if
> fails).
> close and copy only works will valid, open resources.

Sure; a nice strong invariant, and one that's incompatible with move
semantics unless you can figure out how to synthesize an open resource
without throwing.

> This example is not unlike the recent N2698 paper, except that I have
> added copy support too, and fixed an error in operator= (the paper
> called delete when I should have called close).
> http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2698.html
>
> struct resource
> {
>      resource(args...) : h(open(args...)) {}
>
>      resource(const resource& other) : h(copy(other.h)) {}
>
>      resource(resource&& other) : h(other.h)
>      {
>          other.h = 0; // "moved-from" state
>      }
>
>      resource& operator=(const resource& other)
>      {
>          Handle* new_handle = copy(other.h);
>          close(h);
>          h = new_handle;
>      }
>
>      resource& operator=(resource&& other) // caution: self-assignment
> isn't safe
>      {
>          close(h);
>          h = other.h;
>          other.h = 0; // "moved-from" state
>
>          return *this;
>      }
>
>      ~resource()
>      {
>          close(h);
>      }
>
> private:
>      Handle* h;
> };
>
>
> Now, let's make the destructor callable from moved-from objects (first
> bullet).
>      ~resource()
>      {
>          if(h)
>              close(h);
>      }
> This adds one indirection.

I see an if.  I don't quite see that as an indirection.  It added a
test-and-branch.  Typically the cost, with respect to that of
allocating/deallocating the underlying resource, is negligible.  So
what's your concern?

> Now, let's consider making operator= callable from moved-from objects,
> since it is required by std::swap. (second bullet)
>      resource& operator=(const resource& other)
>      {
>          Handle* new_handle = copy(other.h);
>          if(h)
>              close(h);
>          h = new_handle;
>      }

resource& operator=(resource other)
{
     swap(*this,other);
     return *this;
}

>      resource& operator=(resource&& other)
>      {
>          if(h)
>              close(h);
>          h = other.h;
>          other.h = 0; // "moved-from" state
>
>          return *this;
>      }
> This adds one indirection too.

Still not seeing an indirection.

> Let's allow the moved-from objects to be assigned and constructed
> from. (third bullet)
>      resource(const resource& other) : h(other.h ? copy(other.h) : 0)
> {}
>
>      resource& operator=(const resource& other)
>      {
>          Handle* new_handle = other.h ? copy(other.h) : 0;
>          if(h)
>              close(h);
>          h = new_handle;
>      }
> This adds one additional indirection in operator=(lvalue) and one in
> the copy constructor.

OK, leaving aside for the moment my disagreement with your use of the
term "indirection," what's your point?

> I certainly do not allowing moved-from objects to be assigned and
> constructed from, which would require all modifications up to the
> third bullet, not because of its inefficiency, but because it can
> propagate the "moved-from" state, which I consider highly undesirable.
> Such a state should never be accessed to begin with in my opinion!

It's got to be accessed by the destructor at minimum.  Once you allow it
into the class invariant, it's in.

> I would quite like the Standard Library to make promises to not
> perform certain operations on moved-from objects, that is all.

Which operations?  Copy and assign?

>> > Ideally I would personally prefer to restrict to destructor-only. It
>> > adds a little performance penalty which can be avoided altogether. I'm
>> > in a favor of only using destruct/reconstruct on empty types to
>> > perform assignment. Yes, it isn't exception-safe, but restoring an
>> > empty state in case of failure should be nothrow.
>>
>> I don't understand what you're driving at here either.  Could you
>> please spell it out for me?
>
> I believe moved-from objects should not allow anything but
> destruction, and thus there should be no need to support that.

Support what?

> Simply because moved-from objects are supposed to be rvalues, 

No, they're not.  If moving were restricted to rvalues it would prevent
many important optimizations such as the ones we're making in
std::vector.

> and rvalues cannot be assigned to, for example.
>
> Moved-from objects are objects which were casted to rvalues, 

That's a simplified way of looking at it, but in reality std::move
doesn't do any casting.

> and they should be treated as such.

Meaning, "they should never be touched again."  When you operate on (in
this case, move from) an rvalue, that normally means it will be
destroyed before any other code gets to touch it.  Again, that would
prohibit important optimizations.

> There is nothing that can be done on rvalues once they've been moved
> except destructing them (which is done automatically), so casted
> lvalues should, in my opinion, maintain this and thus performing any
> other operation should not be done by any part of the standard
> library.
>
> However, std::swap does it.
>
> void swap(T& a, T& b)
> {
>      T tmp(std::move(a));
>      a = std::move(b);
>      b = std::move(tmp);
> }


So you'd prefer

     void swap(T& a, T& b)
     {
          T tmp(std::move(a));
          a.~T();
          new (&a) T(std::move(b));
          b.~T();
          new (&b) T(std::move(tmp));
     }

??

> The problematic line is a = std::move(b).
> We lied to T::T(T&&) saying that 'a' was an rvalue 

No, we said "you can move from it."

> where it really
> wasn't. We tricked it into believing that. T::T(T&&) could then
> theoretically choose to put 'a' in a state it cannot be assigned-to,
> since rvalues cannot.
> But then, the code would break.
>
> I kind of accuse that swap implementation to be evil.
>
> The alternative solution I propose is to destruct 'a' and construct-
> move 'b' in it instead of performing the assignment.

That's awful, though.  If you're going to force everyone to manually
destroy moved-from values before re-using them, you may as well
implement "destructive move" and have the compiler do it.  But then, we
don't know how to write safe code with destructive move semantics.  If
you can solve that problem, we can talk about it.

>> > That would of course
>> > require the addition of another construction primitive.
>>
>> Nor here.
>
> The aforementioned alternative introduces exception-safety issues.
> I proposed to solve them by introducing a nothrow construction
> primitive that constructs an object directly in moved-from state.

Why would you want to *add* a new way to achieve the state you are
trying to avoid propogating?

> That's however a fairly bad solution, since I can hardly see how it
> could integrate with non move-aware types.
>
>
>> > I would like the standard committee to clearly define guidelines as
>> > to what types ought to do when faced with move semantics, what
>> > operations should still say valid, especially types that aim to
>> > provide a never- empty invariant.
>>
>> But that's not the committee's job, so don't hold your breath.
>
> The standard library is expected to heavily make use of movability, be
> it for containers of algorithms.  I would like to have good guarantees
> about what they'll do.

Sure, I think you do.

> The MoveConstructible and MoveAssignable concepts don't help: they
> require the arguments to be (real) rvalues, 

Where did you get that idea?

> and there is no problem in that case.  std::swap is only supposed to
> require MoveConstructible and MoveAssignable, but since it lied using
> casting, 

It didn't.  There's no casting and no lying.

Of course, you can accuse any code of lying if you make up your own
definitions of things like rvalue reference, but it doesn't hold water.

> it actually passed an lvalue when an rvalue was expected,
> which is the real source of the problem.

I haven't seen a problem yet.

> *I'm afraid there is really a flaw in the standard here*. So either
> the concepts need fixing, or std::swap does.

The fact that you don't like it doesn't make it flawed.

> Two solutions:
> - Require bullet 2 for MoveAssignable types
> - Really treat lvalues casted to rvalues as rvalues (which seems more
> logical to me), that means not accessing them more than once. Think of
> alternative techniques to implement std::swap in terms of moves.
>
>
>> Basically, if you want to provide a never-empty invariant, you have to
>> figure out how to do it so that moved-from objects are not empty, for
>> whatever your definition of "empty" is.
>
> Why?

Because it's *a class invariant*.  That means it's always true outside
of mutating operations between construction and destruction.  That's
just by definition.  http://en.wikipedia.org/wiki/Class_invariant

> Real rvalues can only be accessed once. 

     std::vector<int> const& x = std::vector<int>(10);

now access the rvalue through x as many times as you like.

> I can put them in whatever
> state I like after that, as far as it is destructible it shouldn't
> matter.

Not really; that's why rvalue reference parameters are treated as
lvalues inside functions.

> It's true though that with rvalue references, I can treat rvalues as
> lvalues and access them multiple times. 

You don't need rvalue references to do that as shown above.

> Maybe that is also a defect in itself however.

maybe-you'd-prefer-a-pure-functional-language-ly y'rs,

---
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
David
7/30/2008 8:56:14 PM
On Jul 30, 4:58 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
> On 30 juil, 01:45, Howard Hinnant <howard.hinn...@gmail.com> wrote:
>
> > That is going to depend on what algorithm the type is being used in.
> > For example the current WP says this about swap:
>
> > > Requires: Type T shall be MoveConstructible (33) and MoveAssignable (35).
>
> > Thus, a "moved-from" object can expect to be move-assigned into.  Also
> > there is a blanket Destructible statement somewhere in 17 I believe.
> > So a moved-from type can be expect to be destructed.  swap will do
> > nothing else with an object (it is not permitted to).
>
> I hardly see how those concepts require that.
> They require
> T obj(rv);
> and
> lv = rv;
> to be valid operations, that is all.
> (lv and rv being respectively lvalue and rvalue of type T)
>
> An lvalue shouldn't be in a moved-from state, since moved-from state
> is only required to be reachable from rvalues.

Due to the ability to "cast" an lvalue to an rvalue, an lvalue can be
moved from.  Thus an lvalue can be in a moved-from state.

> If allowing moved-from objects to be assigned-to is the expected
> behaviour, it should require
> T obj(static_cast<T&&>(lv));
> and
> lv = rv;
> to be valid instead.

Please feel free to open an LWG issue on this.  To do so, send me an
email requesting an open issue.  Supply the issue title, a brief
discussion, and preferably a proposed resolution with respect to the
latest working draft.

> After decay from lvalue to rvalue-reference, the information is lost:
>
> void foo(T&& o) // I want to work with a T, potentially modifying it
> and I don't care whether it's an lvalue or an rvalue
> {
>      o.value = 42; // we modify the object
>      some_container c;
>      c.insert(o); // I put my object in my container -- actually
> potentially wrong!
>
> }
>
> Here, the container will treat 'o' as an rvalue even if it was indeed
> an lvalue.

You have incorrectly interpreted the current draft.  "o" is treated as
an lvalue throughout foo() in the example above, even if an rvalue was
bound to "o".  When this code inserts, and assuming "c" is a standard
container, a copy of "o" is put into the container.

> Also, another problems with rvalue references is that they are
> obviously unsafe constructs, since an rvalue is given a name, can be
> accessed multiple times and may even end up as being in an invalid
> "moved-from" state anytime.
>
> For example
>
> void foo(const T&) = delete;
> void foo(T&& o) // I know o is really an rvalue
> {
>      some_container c;
>      c.insert(o); // move my object into the container
>
>      /* Now I shouldn't touch 'o' anymore, but the type system doesn't
> prevent that! */
>      c.insert(o); // move my object again, despite it being invalid --
> may break
>
> }

This is precisely the rationale for treating named rvalue references
as lvalues:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#More%20on%20A&&

> So I think that ideally, in addition to only rvalues binding to rvalue
> references, compilers should perform analysis to make sure rvalue
> references were only accessed once. (Also, accessing only part of the
> object doesn't prevent accessing the other part of the object, but
> prevents accessing the whole object)

Such static analysis is not reliable once run time branching is
introduced into the function.  The static analyzer can't be sure if a
value was moved from under the "if" or not.

if (condition)
    c.insert(move(o));
// o moved from at this point or not?

-Howard



-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Howard
7/30/2008 9:02:58 PM
On Jul 30, 4:58 pm, Mathias Gaunard <loufo...@gmail.com> wrote:

> The alternative solution I propose is to destruct 'a' and construct-
> move 'b' in it instead of performing the assignment.

I have no objection whatsoever to an operation which constructs one
object while destructing another.  In the past I've called it a
destructive move constructor.  You are in good company with such a
proposal (by several experts whom I highly regard).  If it is to
exist, it should exist in addition to (not instead of) the non-
destructive move constructor.  I too believe the destructive move
constructor is valuable.  I was not able to create one.  But I welcome
efforts to do so, as long as they do not attempt to supplant the non-
destructive move constructor but complement it (as the move
constructor complements, not replaces, the copy constructor).

Realistically I suspect such a proposal is too late for C++0X.  But
the time to get started on C++1X is *now*.  I started on the non-
destructive move constructor in 2001.  And I leveraged much technical
discussion on this very newsgroup dating back several years prior to
2001, including some posts nearly identical to your other post today
in this thread regarding the dangers of accidently using moved-from
values.

-Howard



-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Howard
7/30/2008 9:28:21 PM
In article
<47281e11-1e6e-411c-b714-1149790679d8@k37g2000hsf.googlegroups.com>,
  Mathias Gaunard <loufoque@gmail.com> wrote:

> struct resource
> {
>      resource(args...) : h(open(args...)) {}
> 
>      resource(const resource& other) : h(copy(other.h)) {}
> 
>      resource(resource&& other) : h(other.h)
>      {
>          other.h = 0; // "moved-from" state
>      }
> 
>      resource& operator=(const resource& other)
>      {
>          Handle* new_handle = copy(other.h);
>          close(h);
>          h = new_handle;
>      }
> 
>      resource& operator=(resource&& other) // caution: self-assignment
> isn't safe
>      {
>          close(h);
>          h = other.h;
>          other.h = 0; // "moved-from" state
> 
>          return *this;
>      }
> 
>      ~resource()
>      {
>          close(h);
>      }
> 
> private:
>      Handle* h;
> };

This code already feels messy to me.  Things like copy() should only
appear in copy constructor, things like close() should only appear in
the destructor, etc.

> Now, let's make the destructor callable from moved-from objects (first
> bullet).
>      ~resource()
>      {
>          if(h)
>              close(h);
>      }
> This adds one indirection.

So an indirection is basically just an inexpensive check compared with
closing or copying a resource.  Okay.


I would probably rewrite it using the copy and swap idiom inside the
operator=() functions, as in:

struct resource
{
     resource(args...) : h(open(args)) {}

     // Added test for 0 == other.h
     resource(const resource& other) : h(other.h ? copy(other.h) : 0) {}

     resource(resource&& other) : h(other.h) { other.h = 0; }

     // swap two resources
     void swap(resource& other)
     { std::swap(h, other.h); }

     // pass by value, not reference
     resource& operator=(resource other)
     { swap(other); return *this; }

     // perfectly safe with self assignment
     resource& operator=(resource&& other)
     { resource(other).swap(*this); return *this; }

     // Added test for 0 == h
     ~resource() { if (h) close(h); }

private:
      Handle* h;
};

> Now, let's consider making operator= callable from moved-from objects,

Already works in my version; no additional code necessary to handle the
"indirection".

> Let's allow the moved-from objects to be assigned and constructed
> from. [...]
> This adds one additional indirection in operator=(lvalue) and one in
> the copy constructor.

Only because of the way you wrote the code.  In my version, I only had
to add the NULL pointer check to the copy constructor.

> I certainly do not allowing moved-from objects to be assigned and
> constructed from, which would require all modifications up to the
> third bullet, not because of its inefficiency, but because it can
> propagate the "moved-from" state, which I consider highly undesirable.

That is the well known downside to move semantics; one cannot do
everything with a "moved-from" object that one can with a normal object.
If your object supports move semantics, you have to take this into
account.

> I believe moved-from objects should not allow anything but
> destruction, and thus there should be no need to support that.

Well, I can't change your belief system.  I do not see a way to make a
compiler enforce that in the general case.  Take this example:

void DoSomething(T t, bool b)
{
     T   tt;
     if (b)
         tt = std::move(t);
     else
         tt = t;

     // Is t assignable here?
}


> void swap(T& a, T& b)
> {
>      T tmp(std::move(a));
>      a = std::move(b);
>      b = std::move(tmp);
> }
> 
> The problematic line is a = std::move(b).

What is the alternative implementation you are proposing?  Should swap
remain inefficient for objects that implement move semantics?  That
seems wrong.  What about objects that only implement move but not copy
semantics?

> I kind of accuse that swap implementation to be evil.

But it is only evil because you don't believe that moved-from objects
should be assignable.  I might just be reading it wrong, but this looks
like circular logic.

> The alternative solution I propose is to destruct 'a' and construct-
> move 'b' in it instead of performing the assignment.
> The aforementioned alternative introduces exception-safety issues.

That is putting it mildly.

> I proposed to solve them by introducing a nothrow construction
> primitive that constructs an object directly in moved-from state.
> That's however a fairly bad solution, since I can hardly see how it
> could integrate with non move-aware types.

Setting aside that issue for the moment, why is adding the requirement
that its move constructor be non-throwing any better than adding the
requirement that a moved-from object be assignable?

> std::swap is only supposed to require MoveConstructible and
> MoveAssignable, but since it lied using casting, it actually passed an
> lvalue when an rvalue was expected, which is the real source of the
> problem.

Other than requiring MoveConstructible and MoveAssignable for moved-from
objects, I'm not seeing the problem.  If an object doesn't implement
move semantics, it works.  If an object correctly implements move
semantics, it works.

-- 
  Nevin ":-)" Liber  <mailto:nevin@eviloverlord.com>  773 961-1620

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Nevin
7/30/2008 9:28:22 PM
This conversation might benefit from "C++ Rvalue References Explained":
http://thbecker.net/articles/rvalue_references/section_01.html

- Michael Safyan

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Michael
7/31/2008 4:58:20 AM
In article <87zlnz82c6.fsf@mcbain.luannocracy.com>,
  David Abrahams <dave@boostpro.com> wrote:
> on Wed Jul 30 2008, Mathias Gaunard <loufoque-AT-gmail.com> wrote:
> > However, std::swap does it.
> >
> > void swap(T& a, T& b)
> > {
> >      T tmp(std::move(a));
> >      a = std::move(b);
> >      b = std::move(tmp);
> > }
> 
> 
> So you'd prefer
> 
>      void swap(T& a, T& b)
>      {
>           T tmp(std::move(a));
>           a.~T();
>           new (&a) T(std::move(b));
>           b.~T();
>           new (&b) T(std::move(tmp));
>      }

There is a subtle flaw in your implementation which, when fixed, weakens
his proposal even further.

For correctness:

    void swap(T& a, T& b)
    {
       if (boost::addressof(a) != boost::addressof(b))
       {
          T tmp(std::move(a));
          a.~T();
          new (&a) T(std::move(b));
          b.~T();
          new (&b) T(std::move(tmp));
       }
    }

Even though Mr. Gaunard proposed it:

1.  It doesn't get rid of his "indirection" penalty; algorithms would
have to pay it too.

2.  All algorithms now have the burden of preventing self move
assignment, because under his proposal, it would be illegal.

-- 
  Nevin ":-)" Liber  <mailto:nevin@eviloverlord.com>  773 961-1620

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Nevin
7/31/2008 4:58:21 AM
In article <nevin-934990.18470530072008@chi.news.speakeasy.net>,
  "Nevin :-] Liber" <nevin@eviloverlord.com> wrote:

>      resource& operator=(resource&& other)
>      { resource(other).swap(*this); return *this; }

Got that slightly wrong.  I believe it should be:

    resource& operator=(resource&& other)
    { resource(std::move(other)).swap(*this); return *this; }

And that is only if I want the resource originally held by this to be
released (in the non self assignment case) at the end of this assignment.

If that isn't a requirement, the simpler:

    resource& operator=(resource&& other)
    { swap(other); return *this; }

should suffice.

-- 
  Nevin ":-)" Liber  <mailto:nevin@eviloverlord.com>  773 961-1620

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Nevin
7/31/2008 1:00:50 PM
{To moderators: I sent my previous message without finishing writing
by error, please ignore it and replace it by this one. Thanks.}

{Previous article rejected as [overquoted]. To ensure rejection, you
should contact us by e-mail right after a submission by mistake. -mod }

On 31 juil, 05:56, David Abrahams <d...@boostpro.com> wrote:
> on Wed Jul 30 2008, Mathias Gaunard <loufoque-AT-gmail.com> wrote:

> I see an if.  I don't quite see that as an indirection.  It added a
> test-and-branch.

I see that as "close" only having to be called in the case the object
is a "real resource type". Then there is some kind of dynamic dispatch
to do, to dispatch "close" in only that case, hence, an indirection. I
acknowledge that can be confusing since it's mostly used to express
pointers and the like.
It's only a vocabulary matter anyway, not really the point. Let's talk
of branches for now.


> > Now, let's consider making operator= callable from moved-from objects,
> > since it is required by std::swap. (second bullet)
> >      resource& operator=(const resource& other)
> >      {
> >          Handle* new_handle = copy(other.h);
> >          if(h)
> >              close(h);
> >          h = new_handle;
> >      }
>
> resource& operator=(resource other)
> {
>      swap(*this,other);
>      return *this;
>
> }

That seems quite incorrect for copy-assignment.
You probably meant this for operator(resource&& other), the move-
assignment.
Also you probably wanted to say swap(h, other.h).

While it totally keeps the invariant in a nice way, resources are hold
for longer than they have to, making resource management less
efficient than it can be.
If resources are scarce, it can be problematic when dealing with
complex expressions with lots of rvalues.


> OK, leaving aside for the moment my disagreement with your use of the
> term "indirection," what's your point?

The point is that it requires special cases. So an agreement between
the class designer and the generic code that can make use of it need
to be reached.


> > I would quite like the Standard Library to make promises to not
> > perform certain operations on moved-from objects, that is all.
>
> Which operations?  Copy and assign?

The three "bullets" I gave before. Destruction of moved-from objects
and the ones I'm giving just below.


> > I believe moved-from objects should not allow anything but
> > destruction, and thus there should be no need to support that.
>
> Support what?

Assignment to moved-from, assignment from moved-from, and copy from
moved-from.
Supporting assignment to moved-from is an acceptable compromise for
its simplicity and its exception-safety proprieties, but as I said the
others can propagate the moved-from state which is undesirable.

>
> > Simply because moved-from objects are supposed to be rvalues,
>
> No, they're not.  If moving were restricted to rvalues it would prevent
> many important optimizations such as the ones we're making in
> std::vector.

I'm not against moving from lvalues. I would just like that those
moved lvalues be treated as rvalues.


> > and they should be treated as such.
>
> Meaning, "they should never be touched again."  When you operate on (in
> this case, move from) an rvalue, that normally means it will be
> destroyed before any other code gets to touch it.  Again, that would
> prohibit important optimizations.

Such as?
It seems to me that throwability put aside, destructing/reconstructing
should allow pretty much everything.


> So you'd prefer
>
>      void swap(T& a, T& b)
>      {
>           T tmp(std::move(a));
>           a.~T();
>           new (&a) T(std::move(b));
>           b.~T();
>           new (&b) T(std::move(tmp));
>      }
>
> ??

Almost.
Destructing/Constructing b is not needed, since it is not moved-from.

So

void swap(T& a, T& b)
{
     T tmp(std::move(a));
     a.~T();
     new(&a) T(std::move(b));
     b = std::move(a);
}

The basic exception-safety could then be obtained again by

void swap(T& a, T& b)
{
     T tmp(std::move(a));
     a.~T();
     try
     {
         new(&a) T(std::move(b));
     }
     catch(...)
     {
         try
         {
             new(&a) T(std::move(tmp));
         }
         catch(...)
         {
             make_destructable(&a);
         }
         throw;
     }
     b = std::move(a);
}

But maybe std::swap really ought to be strong exception-safe in the
first place.


> > The problematic line is a = std::move(b).
> > We lied to T::T(T&&) saying that 'a' was an rvalue
>
> No, we said "you can move from it."

Well, as far as the type system is concerned, I said it was an rvalue
and not an lvalue.
T::T(T&&) cannot even base its code on that. Should expressions of
type T&& not be treated as rvalues then, but as objects that can be
moved?


> > The MoveConstructible and MoveAssignable concepts don't help: they
> > require the arguments to be (real) rvalues,
>
> Where did you get that idea?

I gave the requirements in first reply to Howard Hinnant in this
thread. As far as I understand them, they do not prevent me from
making a = std::move(b) with 'a' in a moved-from state invalid.
They give what they expect in terms of rvalues, not lvalues casted to
rvalues. If that was expected, maybe saying "any expression binding to
T&&" would have been clearer.


> Because it's *a class invariant*.  That means it's always true outside
> of mutating operations between construction and destruction.  That's
> just by definition.  http://en.wikipedia.org/wiki/Class_invariant
>
> > Real rvalues can only be accessed once.
>
>      std::vector<int> const& x = std::vector<int>(10);

Binding an rvalue to a const-reference involves an rvalue to lvalue
conversion AFAIK, which might even copy.


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Mathias
8/1/2008 5:58:37 PM
On 31 juil, 06:02, Howard Hinnant <howard.hinn...@gmail.com> wrote:
> You have incorrectly interpreted the current draft.  "o" is treated as
> an lvalue throughout foo() in the example above, even if an rvalue was
> bound to "o".  When this code inserts, and assuming "c" is a standard
> container, a copy of "o" is put into the container.

Hum, yes.
Sorry about this. I had a hard time trying to learn what I could grasp
of the working papers to be honest.


>
> Such static analysis is not reliable once run time branching is
> introduced into the function.  The static analyzer can't be sure if a
> value was moved from under the "if" or not.
>
> if (condition)
>     c.insert(move(o));
> // o moved from at this point or not?

Unless you can tell that the condition is always false, then it has
potentially been moved.
So it's to be considered moved.

I only thought of such a mechanism as a warning anyway, kinda like
"function returning T may not return anything" and the like.


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Mathias
8/2/2008 1:33:25 AM
on Fri Aug 01 2008, Mathias Gaunard <loufoque-AT-gmail.com> wrote:

>
>> > Now, let's consider making operator= callable from moved-from objects,
>> > since it is required by std::swap. (second bullet)
>> >      resource& operator=(const resource& other)
>> >      {
>> >          Handle* new_handle = copy(other.h);
>> >          if(h)
>> >              close(h);
>> >          h = new_handle;
>> >      }
>>
>> resource& operator=(resource other)
>> {
>>      swap(*this,other);
>>      return *this;
>>
>> }
>
> That seems quite incorrect for copy-assignment.

??

> You probably meant this for operator(resource&& other), the move-
> assignment.
> Also you probably wanted to say swap(h, other.h).

No, I meant what I wrote.

> While it totally keeps the invariant in a nice way, resources are hold
> for longer than they have to, making resource management less
> efficient than it can be.
> If resources are scarce, it can be problematic when dealing with
> complex expressions with lots of rvalues.

If you're talking of the transient-resource-usage-during-assignment
issue, then yes, I'm aware of that.  However, that only matters in a
small number of cases; the implementation I posted is superior in many
other cases.

>> OK, leaving aside for the moment my disagreement with your use of the
>> term "indirection," what's your point?
>
> The point is that it requires special cases. So an agreement between
> the class designer and the generic code that can make use of it need
> to be reached.

It may require special cases.  It may not.  Many such classes have an
empty state naturally (think std::string).

>> > I would quite like the Standard Library to make promises to not
>> > perform certain operations on moved-from objects, that is all.
>>
>> Which operations?  Copy and assign?
>
> The three "bullets" I gave before. Destruction of moved-from objects
> and the ones I'm giving just below.

I'm sorry; I can't keep that much context active.

>
>> > I believe moved-from objects should not allow anything but
>> > destruction, and thus there should be no need to support that.
>>
>> Support what?
>
> Assignment to moved-from, assignment from moved-from, and copy from
> moved-from.
> Supporting assignment to moved-from is an acceptable compromise for
> its simplicity and its exception-safety proprieties, but as I said the
> others can propagate the moved-from state which is undesirable.

You are free to define a class that doesn't support those operations.
Also, I would be a little surprised if the standard library were going
to use anything other than destruction and assignment on moved-from
objects (separate issues).

>> So you'd prefer
>>
>>      void swap(T& a, T& b)
>>      {
>>           T tmp(std::move(a));
>>           a.~T();
>>           new (&a) T(std::move(b));
                         ^^^^^^^^^^^^
>>           b.~T();
>>           new (&b) T(std::move(tmp));
>>      }
>>
>> ??
>
> Almost.
> Destructing/Constructing b is not needed, since it is not moved-from.

It most certainly is!

-- 
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
David
8/2/2008 3:28:30 AM
On 31 juil, 06:28, "Nevin :-] Liber" <ne...@eviloverlord.com> wrote:
> In article
> <47281e11-1e6e-411c-b714-114979067...@k37g2000hsf.googlegroups.com>,
>   Mathias Gaunard <loufo...@gmail.com> wrote:
>
>
>
> > struct resource
> > {
> >      resource(args...) : h(open(args...)) {}
>
> >      resource(const resource& other) : h(copy(other.h)) {}
>
> >      resource(resource&& other) : h(other.h)
> >      {
> >          other.h = 0; // "moved-from" state
> >      }
>
> >      resource& operator=(const resource& other)
> >      {
> >          Handle* new_handle = copy(other.h);
> >          close(h);
> >          h = new_handle;
> >      }
>
> >      resource& operator=(resource&& other) // caution: self-assignment
> > isn't safe
> >      {
> >          close(h);
> >          h = other.h;
> >          other.h = 0; // "moved-from" state
>
> >          return *this;
> >      }
>
> >      ~resource()
> >      {
> >          close(h);
> >      }
>
> > private:
> >      Handle* h;
> > };
>
> This code already feels messy to me.  Things like copy() should only
> appear in copy constructor, things like close() should only appear in
> the destructor, etc.

You don't like the copy-assignment, it seems.
Copy-assignment copies and destroys the old value. That's the point.
That's what it always does for all types that implement it
(containers, string...).

> I would probably rewrite it using the copy and swap idiom inside the
> operator=() functions, as in:
>
> struct resource
> {
>      resource(args...) : h(open(args)) {}
>
>      // Added test for 0 == other.h
>      resource(const resource& other) : h(other.h ? copy(other.h) : 0) {}
>
>      resource(resource&& other) : h(other.h) { other.h = 0; }
>
>      // swap two resources
>      void swap(resource& other)
>      { std::swap(h, other.h); }
>
>      // pass by value, not reference
>      resource& operator=(resource other)
>      { swap(other); return *this; }
>
>      // perfectly safe with self assignment
>      resource& operator=(resource&& other)
>      { resource(other).swap(*this); return *this; }
>
>      // Added test for 0 == h
>      ~resource() { if (h) close(h); }
>
> private:
>       Handle* h;
>
> };

Oh, that code made me realize I misunderstood David Abrahams' code
higher in the thread (the take by value and swap idiom).
I thought the actual code was clearer. Well.

As for swapping as moving, as I said higher it can be problematic when
dealing with scarce resources, since resources are hold for longer
than they have to.


> > Now, let's consider making operator= callable from moved-from objects,
>
> Already works in my version; no additional code necessary to handle the
> "indirection".

While that idiom is nice, forcing it on people seems bad.
If move constructors throw, std::swap can be problematic, since it is
not strongly exception-safe.


> Only because of the way you wrote the code.  In my version, I only had
> to add the NULL pointer check to the copy constructor.

The special cases are still there and need to be taken consideration
by the developer.
The developer should then now what making its class compatible with
standard utilities requires.

>
> > I certainly do not [like] allowing moved-from objects to be assigned and
> > constructed from, which would require all modifications up to the
> > third bullet, not because of its inefficiency, but because it can
> > propagate the "moved-from" state, which I consider highly undesirable.
>
> That is the well known downside to move semantics; one cannot do
> everything with a "moved-from" object that one can with a normal object.
> If your object supports move semantics, you have to take this into
> account.

Your answer doesn't make much sense to me. I added a word I forgot for
clarification.
I do not see any reason whatsoever to support moved-from objects to be
assigned and constructed from, and since that's really a danger (as if
handling moved-from objects wasn't dangerous enough) I would like it
if it wasn't required.

Note that it is fairly unrelated to supporting assignment to moved-
from objects. While I do not really like that, it is a good
compromise.


> > I believe moved-from objects should not allow anything but
> > destruction, and thus there should be no need to support that.
>
> Well, I can't change your belief system.  I do not see a way to make a
> compiler enforce that in the general case.  Take this example:
>
> void DoSomething(T t, bool b)
> {
>      T   tt;
>      if (b)
>          tt = std::move(t);
>      else
>          tt = t;
>
>      // Is t assignable here?
>
> }

t is an lvalue, it is of course perfectly assignable as far as the
compiler is concerned.
You casted it to rvalue with std::move, so that's your own issue to
make sure you do not use it as an lvalue.

Treating lvalues casted to rvalues as rvalues should be purely the
programmer's job.
Just like a programmer shouldn't modify const objects that were casted
to non-const ones.
Or deference a pointer that points to a T1 when it actually points to
a T2 (T1 and T2 being totally unrelated).
You cast, you take the responsibility for your acts. That's my
opinion.


> > void swap(T& a, T& b)
> > {
> >      T tmp(std::move(a));
> >      a = std::move(b);
> >      b = std::move(tmp);
> > }
>
> > The problematic line is a = std::move(b).
>
> What is the alternative implementation you are proposing?  Should swap
> remain inefficient for objects that implement move semantics?  That
> seems wrong.  What about objects that only implement move but not copy
> semantics?

I fail to see how destruct/reconstruct is less efficient than
assignment, especially for movable types.
By the way it doesn't require protection from self-assignment either,
as you said in other message.

>
> > I kind of accuse that swap implementation to be evil.
>
> But it is only evil because you don't believe that moved-from objects
> should be assignable.

That is evil because rvalues aren't. And that's a (fake) rvalue we
have here.


> > I proposed to solve them by introducing a nothrow construction
> > primitive that constructs an object directly in moved-from state.
> > That's however a fairly bad solution, since I can hardly see how it
> > could integrate with non move-aware types.
>
> Setting aside that issue for the moment, why is adding the requirement
> that its move constructor be non-throwing any better than adding the
> requirement that a moved-from object be assignable?

Where did I talk about non-throwing move constructors?
I talked of a non-throwing constructing primitive to restore
destructible state.

If course, in move constructor doesn't throw, then std::swap is
nothrow and there is no issue altogether (at least in this function).
Making move constructors nothrow would probably magically solve all
exception-safety issues everywhere, but that's not compatible with not
move-aware types.

>
> > std::swap is only supposed to require MoveConstructible and
> > MoveAssignable, but since it lied using casting, it actually passed an
> > lvalue when an rvalue was expected, which is the real source of the
> > problem.
>
> Other than requiring MoveConstructible and MoveAssignable for moved-from
> objects, I'm not seeing the problem.

As I said, I believe that's too much since it can propagate the moved-
from state, which is inherently unsafe.
Requiring
a = rv;
where 'a' is moved-from should be enough.

And ideally, I think it shouldn't be required at all, but it's still
an acceptable compromise in the face of no better solutions to
exception-safety issues.


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Mathias
8/2/2008 3:28:31 AM
on Sat Aug 02 2008, Mathias Gaunard <loufoque-AT-gmail.com> wrote:

>> > Now, let's consider making operator= callable from moved-from objects,
>>
>> Already works in my version; no additional code necessary to handle the
>> "indirection".
>
> While that idiom is nice, forcing it on people seems bad.  If move
> constructors throw, std::swap can be problematic, since it is not
> strongly exception-safe.

Good move constructors don't throw, and that's not imposing a design
burden because it's actually fairly hard to make one that does throw.
swap today can be not-strongly-exception-safe (which is not a problem in
itself) if copy or assignment throws and there's no user-defined swap.
In fact, a strong swap isn't all that interesting.  A nothrow swap (also
not much of a challenge usually) is much more useful.

>> > I certainly do not [like] allowing moved-from objects to be assigned and
>> > constructed from, which would require all modifications up to the
>> > third bullet, not because of its inefficiency, but because it can
>> > propagate the "moved-from" state, which I consider highly undesirable.
>>
>> That is the well known downside to move semantics; one cannot do
>> everything with a "moved-from" object that one can with a normal object.
>> If your object supports move semantics, you have to take this into
>> account.
>
> Your answer doesn't make much sense to me. I added a word I forgot for
> clarification.
> I do not see any reason whatsoever to support moved-from objects to be
> assigned and constructed from, and since that's really a danger (as if
> handling moved-from objects wasn't dangerous enough) I would like it
> if it wasn't required.

I don't see why you should say it's dangerous.  A moved-from object
satisfies all of the class invariants, by definition, so the class
documentation has to tell you if there's something you can't do with the
moved-from state.  But in 90% of cases it's very easy to write your
class so that the moved-from state is exactly as safe to handle as
anything else is, without performance penaltyy.  The other 10% AFAICS,
are essentially pimpls.  In those cases it's very easy to write the
class so that assigning to and destroying moved-from objects is as safe
as ever, and if you want to make it legal to try other things with such
an object it's very easy to make those other operations throw when the
object is empty.  Supporting copy-construction of an object in a
moved-from state (if you want to do that) is a matter of a single check,
and the pass-by-value/swap idiom makes the assignment operator work
automatically.

> Note that it is fairly unrelated to supporting assignment to moved-
> from objects. While I do not really like that, it is a good
> compromise.
>
>> > I believe moved-from objects should not allow anything but
>> > destruction, and thus there should be no need to support that.
>>
>> Well, I can't change your belief system.  I do not see a way to make a
>> compiler enforce that in the general case.  Take this example:
>>
>> void DoSomething(T t, bool b)
>> {
>>      T   tt;
>>      if (b)
>>          tt = std::move(t);
>>      else
>>          tt = t;
>>
>>      // Is t assignable here?
>>
>> }
>
> t is an lvalue, it is of course perfectly assignable as far as the
> compiler is concerned.
> You casted it to rvalue with std::move, so that's your own issue to
> make sure you do not use it as an lvalue.

You mean, by assigning into it.  Sure, you could make the rule that
moved-from objects aren't assignable in your code, but I don't think I
would be willing to live by it.

> Treating lvalues casted to rvalues as rvalues should be purely the
> programmer's job.

If that's your policy.

> Just like a programmer shouldn't modify const objects that were casted
> to non-const ones.

No, that is not the same thing, because that *necessarily* invokes
undefined behavior.

> Or deference a pointer that points to a T1 when it actually points to
> a T2 (T1 and T2 being totally unrelated).

Ditto.  Totally different in nature.

> You cast, you take the responsibility for your acts. That's my
> opinion.

Agreed.  I just don't see std::move as an analogous case.

>> > void swap(T& a, T& b)
>> > {
>> >      T tmp(std::move(a));
>> >      a = std::move(b);
>> >      b = std::move(tmp);
>> > }
>>
>> > The problematic line is a = std::move(b).
>>
>> What is the alternative implementation you are proposing?  Should swap
>> remain inefficient for objects that implement move semantics?  That
>> seems wrong.  What about objects that only implement move but not copy
>> semantics?
>
> I fail to see how destruct/reconstruct is less efficient than
> assignment, especially for movable types.

I know it's not a very common thing, but if a movable type contains a
non-movable resource (say, an embedded buffer), it could be much less
efficient.

>> But it is only evil because you don't believe that moved-from objects
>> should be assignable.
>
> That is evil because rvalues aren't. And that's a (fake) rvalue we
> have here.

Here's a real rvalue being assigned:

   struct X { int array[4]; };
   X f();
   int main()
   {
      f().array[3] = 7;
   }

>> > std::swap is only supposed to require MoveConstructible and
>> > MoveAssignable, but since it lied using casting, it actually passed an
>> > lvalue when an rvalue was expected, which is the real source of the
>> > problem.
>>
>> Other than requiring MoveConstructible and MoveAssignable for moved-from
>> objects, I'm not seeing the problem.
>
> As I said, I believe that's too much since it can propagate the moved-
> from state, which is inherently unsafe.

What requirements would you like to add to swap?

By the way, there is no notion of "moved-from state" in the standard.
If you're suggesting adding one, well that would be a major change of
little benefit IMO.  More importantly, it would be out-of-step with the
standard's mission.  It's not the standard's job to outlaw particular
practices other than as necessary to make it possible to specify the
behavior of things the standard describes.  So for example, in order to
specify the behavior of std::vector<T>, we outlawed exceptions from T's
destructor *in that context*.  The standard doesn't prohibit you from
writing types with throwing destructors, though.

-- 
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
David
8/6/2008 8:28:25 AM
Reply: