5

For instance, if you have a declaration std::unique_ptr<A> a;, then will the following cause problems?

a->foo(std::move(a));

In my case, foo is a virtual function, so I can't just move it out of the class. If the above code turns out to cause problems, then what's an alternative way to have the same effect?

bb94
  • 1,294
  • 1
  • 11
  • 24
  • 4
    Is the function supposed to own the object being pointed for the duration of the call? That's really the the most important question. – StoryTeller - Unslander Monica Nov 19 '17 at 08:07
  • Good question, but not sure what exactly you're asking. The real method used in my case is virtual std::unique_ptr Expression::imbue( std::unique_ptr a, Operator o, size_t precedence, std::unique_ptr b); which is called as such: a = a->imbue(std::move(a), op, std::move(b)); – bb94 Nov 19 '17 at 08:11
  • Are you sure you want to call `std::move` on a class which method is going to be invoked? At the time that `imbue` will get invoked your `a` will be destroyed, so the method will most likely have invalid context. – user3707125 Nov 19 '17 at 08:19
  • 2
    @user3707125 - *"At the time that imbue will get invoked your a will be destroyed*" No it won't. The object is alive and owned by a unique pointer for the duration of the call. The object is destroyed immediately upon exit, and not sooner. – StoryTeller - Unslander Monica Nov 19 '17 at 08:20
  • 1
    @StoryTeller I mean destroyed by the `std::move` operation, not in terms of memory deallocation or stuff like that, but in terms that its data will no longer be valid. – user3707125 Nov 19 '17 at 08:21
  • @user3707125 - The move doesn't destroy anything. It changes the state of `a` outside the function. The `a` inside the function is another object that points at the same pointee. – StoryTeller - Unslander Monica Nov 19 '17 at 08:27

2 Answers2

5

C++11 and C++14

It depends on the signature of foo:

  • If it is foo(std::unique_ptr<A> &&), then the call is safe because the pointer retains the old value until foo begins execution. Whether or not foo changes the pointer is not relevant because any statement from a called function's execution is sequenced after the evaluation of the expression that names the function.
  • If it is foo(std::unique_ptr<A>), then the call has undefined behavior. Though there is a rule that the operands of an operator are evaluated before the computation of the operator's result (so you must have evaluated a before you can know whose foo to call, and therefore call it), this is not enough here, because there is no rule saying that function arguments are sequenced after the function name expression. Therefore, a may have been moved from, and the call to a.operator ->() contains an attempt to dereference a null pointer.

C++17

Since C++17, it is safe. Several new sequencing rules were added, one of which addresses this case precisely:

In a function-call expression, the expression that names the function is sequenced before every argument expression and every default argument.

Source

Ah, well, I should post a safe version for pre-17 C++. It's very simple, just add the necessary sequencing yourself, e.g. by using two statements:

auto aRaw = a.get();
aRaw->foo(std::move(a));
Arne Vogel
  • 6,346
  • 2
  • 18
  • 31
1

I don't see why it wouldn't be legitimate.

According to this comment:

a = a->imbue(std::move(a), op, std::move(b));

it looks like what you want is a consume operation. That is, the function consumes its inputs (ownership is transferred into the function, and they're deleted upon exiting), which is just what happens.

One of the being-deleted objects happens to be the one you call the member function on, but although that looks like it might be tricky, it sure is OK because at the time of the calling, the object is still guaranteed to live.

You return an object (presumably move since it's an unique_ptr again) which is reassigned to the original name. So instead of c = a->imbue(a, op, b) it's a= a->imbue(a, op, b).

That's OK, why would it not be. It's incidentially the same name like the one of an object that just got deleted, but so what. The object is valid, and there's nothing in your way of reassigning something different to a name. You reassign something different to a name when you write x = x + 1; as well, and nobody would object to that.

Something you may definitively want to avoid is (obviously, but maybe not so obvious?) calling imbue with the same Expression object in both locations.

Damon
  • 67,688
  • 20
  • 135
  • 185