On Thursday, April 28, 2016 at 11:25:38 AM UTC+10, Ehsan Akhgari wrote:
> On 2016-04-28 9:00 AM, Gerald Squelart wrote:
> > On Thursday, April 28, 2016 at 10:41:21 AM UTC+10, Ehsan Akhgari wrote:
> >> On 2016-04-28 8:00 AM, Gerald Squelart wrote:
> >>> On Thursday, April 28, 2016 at 9:35:56 AM UTC+10, Kyle Huey wrote:
> >>>> Can we catch this pattern with a compiler somehow?
> >>>>
> >>>> Foo foo;
> >>>> foo.x = thing;
> >>>> DoBar(mozilla::Move(foo));
> >>>> if (foo.x) { /* do stuff */ }
> >>
> >> I think so. We already have an analysis which would detect whether the
> >> return value of a function is used somewhere or not. We should be able
> >> to reuse that to find the call to DoBar(), and then look for future
> >> occurrences of foo used as an rvalue in the rest of the function. Once
> >> we detect a use of "foo" as an lvalue, further usages of it as an rvalue
> >> in the same function should be OK and not trigger the error. File a bug?
> >>
> >>> Definitely something that would be nice.
> >>>
> >>> But if we have/implement such a catcher, I'd like to have an annotation
> >>> to say "yep I really want to reuse this moved-from object".
> >>> Because sometimes the function will choose not to actually move from an
> >>> rvalue-ref, or the object knows to revert to a fully-reusable state, etc.
> >>
> >> What you're describing sounds like a violation of move semantics, right?
> >> The first case should only happen if DoBar doesn't accept an rvalue
> >> reference, in which case the code above is definitely doing something
> >> that the author did not expect, given that they have used Move(). The
> >> latter case sounds completely broken, and if there is an actual good use
> >> case for it, the C++ move semantics sound like the wrong tool to achieve
> >> that goal to me.
> >>
> >> If you feel like I'm missing something or you can make a strong argument
> >> on why breaking move semantics is OK in some cases, please let me know.
> >> :-)
> >>
> >> Cheers,
> >> Ehsan
> >
> > std::move and mozilla:Move are just casts that make an l-value object
> > *look* like an r-value, so that when the compiler considers which 'DoBar'
> > to use, one that takes an r-value reference will be picked first.
> >
> > "Move" is probably not the best name because it gives this impression that
> > an actual move happens, but that's what we're stuck with in the standard.
>
> Yes, I understand that.
>
> > I don't see a "violation of move semantics" in there, could you please
> > elaborate on what exact move semantics are violated?
> > I'd say it's probably more a "perversion of the move spirit". :-)
>
> Even though Move() doesn't "actually" move anything, it has a meaning to
> the author: "when I have an lvalue object, I want to move it to some
> other place".
>
> While it's true that if DoBar() doesn't really move its argument
> somewhere the code compiles, in that case DoBar() is breaking the
> expectation of the caller since the caller clearly intended for a move
> to happen. In C++ there is no way for the author to know that a move
> actually happened, so the semantics of using the object after the move
> are fuzzy. This is not really helpful since there is no clear way to
> "check" whether a move actually happened, and it's not quite clear how
> the caller should act if the callee decides to not move. IOW, without
> something enforcing that a move actually happens, it is impossible to
> understand the code Kyle posted above without knowing the implementation
> of DoBar(), which is a problem with the C++ move semantics.
>
> However if we change the contract so that the callee is expected to
> always perform a move, then we can perform a useful check on the caller
> side to enforce not touching the object after it has been moved, which
> is what Kyle was asking for.
>
> > In any case, a moved-from object *must* stay valid after that call, because
> > at the minimum it will be destroyed at the end of its enclosing scope, by
> > invoking its normal destructor, no magic or special path there.
>
> Define valid. The object will not be *destroyed* after a move, but it
> should be "semantically empty" (the definition of "semantically empty"
> is different depending on the class of the object.)
>
> I can see how it could be useful to for example call some methods on the
> object such as IsEmpty(), but using the object willy-nilly is not OK
> unless if we want to preserve the loose built-in C++ semantics where
> Move() doesn't really mean much if anything unless if stars collide and
> the callee and the caller both do something in concert. :-)
>
> > Now what to do with a moved-from object, is I think more a philosophical
> > question! Some argue that we should do nothing (except the inevitable
> > implied destruction). Others think it should be fine to also allow
> > re-assignment (i.e. reuse the variable for something completely different).
> > And yet others would allow total reuse.
>
> I don't think it's a philosophical question at all, quite to the
> contrary it is a very practical question. Using such an object as an
> lvalue is fine (the re-assignment case for example). Using the object
> is any other way is not. Maybe you'll get away with that for *some*
> classes, but we're talking about enforcing a more strict set of
> semantics on our code which allows us to actually reason about what happens.
Ok, maybe not philosophical, but definitely bikesheddingly a coding style issue
which we (the Mozilla developers) should discuss and decide upon -- as I think
is what is happening here. ;-)
> > My position is that 'Move(x)' *usually* means we give the value away and
> > don't want to use it again, and therefore a compiler warning/error would
> > help catch unexpected reuses; but also that some situations call for reuse
> > (e.g. the function doesn't always steal the object's contents, based on
> > other factors) and a programmer in the know should be allowed to annotate
> > this special case.
>
> Can you give a practical example of when a sane function would choose to
> move some of the times but not others, and also explain how the caller
> is supposed to know what to do, and also how it is supposed to know
> whether the callee may not move in the first place?
Say we have a resource held in a UniquePtr. Then I may have a number of
potential suitors that could handle its content, so I will go through that list
and for each, I will say 'take this if you want it', until someone actually
takes it.
E.g.:
UniquePtr<MediaFile> file;
for (auto& reader : mMediaReaders) {
reader.TakeMediaIfKnown(file);
if (!file) { // 'file' is now nullptr, reader eated it, we're done.
break;
}
}
if (file) {
ReportFailure(file);
}
and a reader's method could look like:
void MP4Reader::TakeMediaIfKnown(UniquePtr<MediaFile>&& aFile)
{
if (aFile && aFile->HasMimeType("video/mp4") {
mMyFileUniquePtr = Move(aFile); // Actual move.
...
}
}
> > Note that we talked a bit about this situation in:
> > https://groups.google.com/d/topic/mozilla.dev.platform/VLtl2yL_TlA/discussion
> > Referring to:
> > http://mxr.mozilla.org/mozilla-central/source/mfbt/UniquePtr.h#183
> > Which talks about conditionally moving from a UniquePtr.
>
> I had missed that thread, but it seems like in that thread you're half
> agreeing with me, unless I'm missing something? :-)
I somewhat changed my mind. Can I change yours? ;-)
Anyway, how about this:
- mozilla::Move() is reserved for expected moves, and *any* re-use after that
is considered an error.
- We introduce something like mozilla::MakeAvailableForMove(), which allows for
re-use.
- For extra safety, we could allow MakeAvailableForMove() to only work on
classes that have a special attribute, e.g. MOZ_TYPE_IS_REUSABLE_AFTER_MOVE.
_______________________________________________
dev-platform mailing list
[email protected]
https://lists.mozilla.org/listinfo/dev-platform