Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use boost::optional when I can return a pointer [closed]

If I have a find function that can sometimes fail to find the required thing, I tend to make that function return a pointer such that a nullptr indicates that the thing was not found.

E.g.

Student* SomeClass::findStudent(/** some criteria. */)

If the Student exists, it will return a pointer to the found Student object, otherwise it will return nullptr.

I've seen boost::optional advocated for this purpose as well. E.g. 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"?

My question is, isn't returning a pointer the best solution in this case. i.e. There is a possibility that the queried item will not be found, in which case returning nullptr is a perfect solution. What is the advantage of using something like boost::optional (or any other similar solution)?

Note that, in my example, findStudent will only ever return a pointer to an object that is owned by SomeClass.

like image 249
nappyfalcon Avatar asked Feb 09 '16 15:02

nappyfalcon


4 Answers

optional<T&> was removed from the C++ standardization track because its use is questionable: it behaves nearly identically to a non-owning T* with slightly different (and confusingly different from optional<T> and T*) semantics.

optional<T&> is basically a non-owning T* wrapped up pretty, and somewhat strangely.


Now, optional<T> is a different beast.

I have used optional<Iterator> in my container-based find algorithms. Instead of returning end(), I return the empty optional. This lets users determine without a comparison if they have failed to find the item, and lets code like:

if(linear_search_for( vec, item))

work, while the same algorithm also lets you get at both the item and the location of the item in the container if you actually need it.

Pointers to elements doesn't give you the location information you might want except with contiguous containers.

So here, I've created a nullable iterator that has the advantages of iterators (generically working with different types of containers) and pointers (can be tested for the null state).

The next use is actually returning a value. Suppose you have a function that calculates a rectangle.

Rect GetRect();

now, this is great. But what if the question can be meaningless? Well, one approach is to return an empty rect or other "flag" value.

Optional lets you communicate that it can return a rect, or nothing, and not use the empty rect for the "nothing" state. It makes the return value nullable.

int GetValue();

is a better example. An invalid value could use a flag state of the int -- say -1 -- but that forces every user of your function to look up and track the flag state, and not accidentally treat it as a normal state.

Instead, optional<int> GetValue() makes it clear that it can fail, and what the failure state it. If it is populated, you know it is a real value, and not a flag value.

In both of these cases, returning a non-owning pointer is non-viable, because who owns the storage? Returning an owning pointer is expensive, because pointless heap allocations are pointless.

Optionals are nullable value types. When you want to manage resources locally, and you still want an empty state, they make it clear.


Another thing to look into is the expected type being proposed. This is an optional, but when in the empty state contains a reason why it is empty.

like image 52
Yakk - Adam Nevraumont Avatar answered Oct 21 '22 13:10

Yakk - Adam Nevraumont


The advantage of an optional<Student&> return type here is that the semantics of usage are readily apparent to all users that are familiar with optional (and will become readily apparent once they familiarize themselves with it). Those semantics are:

  • The caller does not own the Student and is not responsible for memory management. The caller simply gets a reference to an existing object.
  • It is clear that this function can fail. You maybe get a value and you maybe get nothing. It is clear that the caller needs to check the result one way or the other.

optional<T> is self-documenting in a way that T* isn't. Moreover, it has other benefits in that it can work in cases where you want to return any kind of object type without the need for allocation. What if you needed to return an int or double or SomePOD?

like image 27
Barry Avatar answered Oct 21 '22 13:10

Barry


Lets consider you have a std::map<IndexType, ValueType> where you are trying to find something (Note: The same applies for other containers, this is just to have an example). You have these options:

  • You return a ValueType&: The user can modify your map-content and does not need to think about memory-allocation/deallocation. But if you dont find anything in your map, you need to throw an exception or something similar.
  • You return a ValueType*: The user can modify your map-content and you can return a nullptr if you dont find anything. But the user can call delete on that pointer and you must specify anyhow if he has to do so or not.
  • You return a smart pointer to ValueType: The user does not have to worry about delete or not-delete and can modify your map-content depending on the type of smart-pointer. You can also return a nullptr. But this pretty much requires you to deal with smart_pointers in your map, which is overly complicated if ValueType would be e.g. just an int otherwise.
  • You return a simple ValueType: The user can not modify your map-content and does not need to think about memory-allocation/deallocation. But if you dont find anything in your map, you need to return some special ValueType which tells the user you didn't find anything. In case your ValueType is e.g. int, which one would you return that makes clear "no int found".
  • You return a boost::optional, which is the closest you can get to a simple ValueType return by value with the additional option of "not returning a ValueType"
like image 32
Anedar Avatar answered Oct 21 '22 11:10

Anedar


optional<T&> may indeed be replaced by T* but T* has not clear semantic (ownership ?).

But optional<T> cannot be replaced by T*. For example:

optional<Interval> ComputeOverlap(const Interval&, const Interval&);

If there is no overlap, no problem with T* (nullptr) or optional<T>. But if there is an overlap, we need to create a new interval. We may return a smart_pointer in this case, or optional.

like image 35
Jarod42 Avatar answered Oct 21 '22 13:10

Jarod42