When to use boost::optional and when to use std::unique_ptr in cases when you want to implement a function that can return "nothing"?
Asked Answered
M

7

24

From what I understand there are 2* ways you can implement a function that sometimes doesnt return a result(for example is person found in a list of ppl).

*- we ignore raw ptr version, pair with a bool flag, and exception when none found version.

boost::optional<Person> findPersonInList();

or

std::unique_ptr<Person> findPersonInList();

So are there any reasons to prefere one over the other?

Middleweight answered 16/1, 2013 at 14:31 Comment(7)
"*- we ignore raw ptr version, pair with a bool flag." -> what the?... An unique_ptr is very inappropriate for a find operation, a raw pointer is exactly what you need, and a bool flag is completely unnecessary!Marxist
Easy: always boost::optional.Soursop
Erm, why is ownership involved?Colchis
@R.MartinhoFernandes: that kinda bother me too; there is a big difference between returning handles and copies.Bleacher
@R.MartinhoFernandes because Im not a good programmer. But nice ppl on SO answered my Q so I learned something today. :)Middleweight
That's ok. I was just wondering if there was some ulterior motivation that might influence a potential answer.Colchis
@R.MartinhoFernandes actually "motivation" was that I saw Bartosz Milewski lectures where he used uptr for this: if interested google "Functional Patterns in C++ youtube" and I wasnt smart enough to figure out what should I use.Middleweight
B
27

It depends: do you wish to return a handle or a copy.

If you wish to return a handle:

  • Person*
  • boost::optional<Person&>

are both acceptable choices. I tend to use a Ptr<Person> class which throws in case of null access, but that's my paranoia.

If you wish to return a copy:

  • boost::optional<Person> for non polymorphic classes
  • std::unique_ptr<Person> for polymorphic classes

because dynamic allocation incurs an overhead, so you only use it when necessary.

Bleacher answered 16/1, 2013 at 14:46 Comment(15)
Don't use pointers and you won't have to throw on null access (you should almost never have null pointers).Explicable
@CatPlusPlus: So, you would rather throw NotFound than NullPointer ? What is the difference ?Bleacher
@MatthieuM. [Good answer] In reply to your comment: NotFound is clearly preferred, once again because it expresses the semantic more directly. However, I think throwing in this context is not a good option anyway.Soursop
@KonradRudolph: I tend to use the null pointer alternative because it's then up to the client to decide whether to check explicitly or rely on the exception. If the client expect potential nullity, then they better check explicitly (an if with early return is less bulky than nesting try/catch) and if they do not expect it (it does not make sense this time) they are still protected.Bleacher
regarding overhead, how is boost::optional implemented, is it just a bool + "contained stuff" ? . I know it has member functions, Im talking about mem layoutMiddleweight
@NoSenseEtAl: it's packed (entirely stack-allocated), though I don't know the exact details. I imagine that std::aligned_storage<sizeof(T), alignof(T)>, bool is as good as anything else.Bleacher
@MatthieuM. hahaha you made that mistake that everyone makes: it's aligned_storage<...>::type ;)Colchis
@R.MartinhoFernandes: damned! :pBleacher
I agree that the dynamic allocation required for unique_ptr is usually a disadvantage but couldn't it also be an advantage? E.g if Person is large and often not found then use of unique_ptr would often avoid allocating a large object.Serviette
@ChrisDrew: You mean avoiding allocating a large object on the stack (to avoid stackoverflow)? It's a very rare corner case, and you'll find yourself in either of two situations: either the object is big, so you allocate it on the heap by yourself, and in this case it's already handled by a smart pointer OR the object is handled through a by-value wrapper (which internally manages the heap allocation) and optional is a natural fit.Bleacher
@MatthieuM No, I mean if you are expecting to return a lot of empty objects then a null unique_ptr is just the size of a pointer, an empty optional<Person> is at least as large as a Person.Serviette
@ChrisDrew: I am afraid I fail to understand the issue. I would need you to clarify why you care about the size of the return type; at the programming language level it does not matter, so I can guess it's an implementation issue, but it appears insufficient to answer your question.Bleacher
Why the distinction between polymorphic and non-polymorphic classes?Fleshy
@phant0m: You cannot store any derived class of Base in a Base value, you can only store it behind a pointer, because their size is unknown.Bleacher
@MatthieuM. Ah I see, thanks! I didn't realize the optional actually stores the value itself.Fleshy
S
14

The general answer is that your intentions are expressed by boost::optional and not by std::unique_ptr. That said, your special case of a find operation should probably conform to the standard library way of doing it, assuming that your underlying type has a concept of iterators: return an iterator to end() if no element is found, and an iterator to the element otherwise.

Soursop answered 16/1, 2013 at 14:43 Comment(0)
H
8

boost::optional more clearly states your intention. You need to document explicitly that empty std::unique_ptr means there's no value to return

Habana answered 16/1, 2013 at 14:40 Comment(6)
I disagree, a null pointer (whether raw or smart) has signaled the absence of result for decades, it's ubiquituous.Bleacher
@MatthieuM.: probably I explained it wrong. If a function returns a std::unique_ptr, it's not obvious that it can return no value, because similar function that always returns something would have the same signatureHabana
We will have to agree to disagree then, for me pointer always rhymes with possible nullity... but then I am paranoid.Bleacher
@MatthieuM. “more clearly states your intent” is unambiguous. boost::optional’s purpose is to state this particular intent. So it’s – pretty much by definition – clearer. There’s nothing to disagree here, this is a factual argument. Furthermore, you forget about the inherent semantic of unique_ptr: it signals (exclusive) ownership.Soursop
@KonradRudolph: I agree boost::optional state the intent more clearly, but if you look at my answer you will see that there are potential issues with it. It's meant to contain a value, not a dynamically allocated class, and therefore is not suitable for owned instances of polymorphic classes. There you use std::unique_ptr, and there is no point in wrapping it in boost::optional on top to express it might be null.Bleacher
@MatthieuM as you say, a null pointer has signaled the absence of a result for decades - doesn't mean that it was a good idea though: link Tony Hoare / Historically Bad Ideas: "Null References: The Billion Dollar Mistake" Abstract: I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W) ...Coldhearted
P
6

There is a fourth way of doing it : make the function throw an exception if there is nothing found.

I know this does not really answer your question and therefore I apologize but maybe you hadn't thought about it.

Patti answered 16/1, 2013 at 14:39 Comment(4)
hi, I was planning to add that in * :) but still tnx. basically I dont like using ex as an message mechanism. :DMiddleweight
If the collection can just not have the element you're looking for, exceptions are totally not suitable for that. I can imagine looking for different phrases that don't get any results I'm looking for very fast; combined with exception overhead, looks like a catastrophe.Jerboa
For me the exception overhead is not at runtime, it's in the source code. try-catch introduces two separate scopes and you need gymnastics to even get anything out of them (i.e. you need to declare a variable out of the try-catch, so you can use it later; are you going to make everything default constructible?).Colchis
@R.MartinhoFernandes: who needs class invariants ?Bleacher
J
5

Ah, Xeo didn't show up yet?

Well, I told you this once, and I'll tell it again: these two are completely different objects with different purposes.

  • unique_ptr means I own an object. It's just different way of saying "I am an object". Thus, null unique_ptr is something that can attract attention. I was expecting an object, but I got nothing; the code must be wrong!
  • optional means I can sometimes be not initialized, but that's ok. In this case, there's no worries; if it's None, it's the behavior that has been thought of.

The fact that both implicitly convert to bool doesn't mean they can be used interchargeably. Use optional with code that is likely not to produce any output (reading a stream, for example). Use unique_ptr in factory objects; they will most likely create an object for you, and if not, throw an exception.

A conclusion regarding your example : find should return optional.

Jerboa answered 16/1, 2013 at 14:46 Comment(1)
I don't understand this fear of null unique_ptr. There is nothing in the C++ standard that says a unique_ptr should not be null. And a null unique_ptr expresses "not found" to me perfectly. In just the same way optional does. The real advantage of optional in mind is that you don't have to pay the cost of a dynamic memory allocation like Matthieu says.Serviette
R
3

Conceptually boils down to this, given the nullable requirement:

std::optional has value semantics, stack store.

std::unique_ptr has move semantics, heap store.

If you want value semantics and heap store, use std::indirect http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0201r1.pdf .

(If you want move semantics and stack store... I don't know. I guess it is a unique_ptr with a stack allocator?)

Revet answered 11/5, 2017 at 22:38 Comment(0)
O
1

So are there any reasons to prefere one over the other?

They express very different intent: optional says that the function may not give a result to give you (and that is not an error case). unique_ptr tells you something about ownership semantics (and is more acceptable to use with null, to express an error).

Usually I would use the one that expresses best the intent behind the interface.

For example, consider that you were writing a HTTP server, that attempts to parse a received buffer into a HTTP request object. When you attempt to parse an incomplete buffer, there is no error case, you simply have to wait and buffer more data, then try again.

I would express this using optional,to make it clear that the function may not return anything (and not returning anything is not an error case).

In case my parsing had to validate stuff (e.g. a regex parser should yield an error if the parsed expression is invalid regex) I would return a null unique_ptr, or better yet, throw an exception.

Outward answered 16/1, 2013 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.