Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

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?

like image 847
bb94 Avatar asked Nov 19 '17 08:11

bb94


People also ask

Can unique_ptr be passed by reference?

Because the unique pointer does not have a copy constructor. Hence you cannot pass it by value, because passing by value requires making a copy.

How do I pass a unique_ptr argument to a constructor or a function?

You cannot copy a unique_ptr . You can only move it. The proper way to do this is with the std::move standard library function. If you take a unique_ptr by value, you can move from it freely.

When should we use unique_ptr?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.

What happens when you copy a unique_ptr?

A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.


Video Answer


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));
like image 147
Arne Vogel Avatar answered Oct 02 '22 11:10

Arne Vogel


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.

like image 43
Damon Avatar answered Oct 02 '22 12:10

Damon