Re: Move semantics and moved/empty objects
{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! ]