6

Can I pass a unique_ptr's reference to a function? If not why should I avoid it?

Ex:

void func(unique_ptr<Clss>& ref);

main() {
   unique_ptr<Clss> a = std::make_unique<Clss>();
   fn(a);
}
Nujufas
  • 676
  • 1
  • 9
  • 19
  • 1
    the question is rather unclear. You can ask you compiler if it is possible, and if it is not, then there is no point in avoiding it – 463035818_is_not_an_ai Feb 19 '18 at 10:22
  • Compiler allows this - but is there any issues which arises upon using it? – Nujufas Feb 19 '18 at 10:23
  • I'd say it depend on the function, really. – StoryTeller - Unslander Monica Feb 19 '18 at 10:25
  • It lets you modify the `unique_ptr` in-place, as expected. The weird thing to do would be to pass it by const-ref which doesn't contribute anything much of value and usually indicates poorly-thought-through design. Both will compile though. – Alex Celeste Feb 19 '18 at 10:25
  • 2
    @Leushenko - On the contrary. Passing by a const-ref is the one that shouldn't raise eyebrows. You let the function refer to the object, even letting it know there's only one owner, without passing ownership. Perfectly reasonable. – StoryTeller - Unslander Monica Feb 19 '18 at 10:27
  • @StoryTeller what does this achieve over passing the pointee by const-ref? The user shouldn't normally need to know how many owners there are if it isn't taking ownership itself, which it can't do through a const-ref to a `unique_ptr`. It's an extra indirection that risks becoming null and not much else. – Alex Celeste Feb 19 '18 at 10:28
  • a function should only take a smart-pointer as a parameter, if it needs to access/change the ownership. otherwise, take a reference to the pointee. – sp2danny Feb 19 '18 at 10:28
  • 4
    Herb Sutter gives a good explanation of different way to pass parameters and how to interpret and choose among them, including using references to smart pointers: https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ – sgvd Feb 19 '18 at 10:29
  • @Leushenko - Passing by const-ref doesn't encode ownership data (and it's not the same as passing a const ref to a pointer, anyway). And personally, I'd rather not have an innocent call like `fn(a)` result in `a` being empty. Well, not unless I have to do it, and the function has a damn awesome name that doesn't leave room for misunderstandings. I don't like it for the exact same reason you listed. `a` becoming null right under me. – StoryTeller - Unslander Monica Feb 19 '18 at 10:48
  • @StoryTeller I'm lost as to the utility of encoding ownership data for an object that you can't share or modify the ownership of. It's a meaningless constraint. `T const &` doesn't become null/invalid during the call unless your preconditions are very broken. It's `unique_ptr const &` that might. – Alex Celeste Feb 19 '18 at 11:13
  • @Leushenko - We are obviously at an impasse here. I'll just end with saying that my position is also the responsible for why I liked seccpur's answer. The modification to the pointer is not done without the caller knowing. Very important if overloading is brought into the mix. Of course, that can start a whole other discussion. – StoryTeller - Unslander Monica Feb 19 '18 at 11:17

5 Answers5

7

Can I pass a unique_ptr's reference to a function?

Yes, a unique_ptr is class like any other.

You should do this when you want to mutate an existing unique_ptr from a function (e.g. calling .reset() on it).

If only you want to access the object inside unique_ptr<T>, take T& or const T& in your function interfaces, so that they can be used independently of unique_ptr.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
5

According to Herb Sutter:

https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

Passing unique_ptr by reference is for in/out unique_ptr parameters.

void f( unique_ptr<widget>& ); // (d)

This should only be used to accept an in/out unique_ptr, when the function is supposed to actually accept an existing unique_ptr and potentially modify it to refer to a different object. It is a bad way to just accept a widget, because it is restricted to a particular lifetime strategy in the caller.

Thanks @sgvd

DevSolar
  • 67,862
  • 21
  • 134
  • 209
Nujufas
  • 676
  • 1
  • 9
  • 19
  • Good that you have added the link, but I would suggest copy more text from the link, since it difficult to understand the context without checking out the link. – Gaurav Nov 26 '20 at 17:31
3

See this code snippet, two ways of passing unique_ptr as function parameter. fun1 will take ownership of the object ( hence should be forwarded from the caller) but func2 commits thats the reference unique_ptr object will be passed as reference and will not be modified by the function.

void func1(unique_ptr<Clss>&& moved_obj) // this function takes ownership
{
    //do something with the moved_obj

    moved_obj.reset();
}

void func2(const unique_ptr<Clss>& ref_obj) // this function takes reference
{
   unique_ptr<Clss> new_obj = std::make_unique<Clss>(*(ref_obj));
}


int main() {

   unique_ptr<Clss> a = std::make_unique<Clss>();
   func1(std::move(a));

   unique_ptr<Clss> b = std::make_unique<Clss>();
   func2(b);

    return 0;
}
seccpur
  • 4,996
  • 2
  • 13
  • 21
  • 1
    Up-voted. Though I must say it feels odd to have to write `std::move` where an object may not be moved. Would have been happier with something like `func1(concede_ownership(p));` but that's neither here nor there, and I'm just rambling. – StoryTeller - Unslander Monica Feb 19 '18 at 11:03
  • 3
    Better to have `void func2(const Clss &)` here. – Jarod42 Feb 19 '18 at 11:05
  • Shouldn't `func2(a);` be `func2(b);`? Passing the moved-from pointer again is not a good idea. – PaulR Feb 19 '18 at 11:44
  • @PaulR: Thanks, typos. Edited – seccpur Feb 19 '18 at 11:45
  • `// this function takes ownership` is incorrect. A rvalue reference parameter does not mean the function takes ownership, just that it *can* take ownership. Only if it actually moves the thing it is referring to will ownership actually be transferred. Otherwise you just have a reference to a unique_ptr like the second function, albeit one that lets you modify that passed object. – NathanOliver Sep 16 '22 at 12:28
2

I see those valid meaningful signatures:

void take_ownership(std::unique_ptr<Clss>);
// or void take_ownership(std::unique_ptr<Clss>&&);

void use(/*const*/ Clss*);
// or void use(/*const*/ Clss&);

std::unique_ptr<Clss> create(/*args..*/);

void might_take_ownership(std::unique_ptr<Clss>&);

The later might make sense, but it is more difficult to reason with (as any I/O argument).

If you can, prefer one of the other.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

Passing a std::unique_ptr to a function is perfectly fine as long as you respect the same rules you would when passing a regular reference to a function:

  • the underlying std::unique_ptr and the object it points to must live at least as long as the function will use the reference;
  • in case of multi-threading, barriers must be put in place.
YSC
  • 38,212
  • 9
  • 96
  • 149