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. > 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? _______________________________________________ dev-platform mailing list dev-platform@lists.mozilla.org https://lists.mozilla.org/listinfo/dev-platform