Is it legal to call a method on a class with a unique_ptr as an argument?

Viewed 884

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?

2 Answers

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));

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.

Related