What are the advantages/disadvantages of std::optional over nullptr?
Asked Answered
S

3

8

I looked several online std::optional documentary over the internet. However I could not be able to find any direct comparison between two cases below:

case 1:

SomePointer* foo::get_some_pointer(cont int value) {
    
    auto result = myMap.find(value);

    if (result != myMap.end()) {
        return const_cast<SomePointer*>(&result->second);
    }

    return nullptr;
}

case 2

 std::optional<SomePointer*> foo::get_some_pointer (cont int value) {
    
        auto result = myMap.find(value);
    
        if (result != myMap.end()) {
            return std::optional<SomePointer*>{&result->second};
        }
    
        return std::nullopt;
    }

What are the advantages/disadvantages of the case 1 over the case 2(nullopt over nullptr) ?

Smash answered 4/11, 2021 at 11:32 Comment(9)
So, for returning a ponter there is no actual need to wrap it to std::optional because of you have nullptr. But std::optional becomes really helpful if you return some objects or values which do not have 'empty' state.Footstall
Case 2 has more nulls, it supports both nullptr and std::nullopt, more choice :). It's more usual to use std::optional<SomePointer> without the * in there.Bigwig
Why should we return copy? Instead of a pointer? @dratenikSmash
That const_cast is extremely sketchy, by the way.Citation
std::optional allows returning objects by value (which you don't in your example, though) and still have the option available of not returning anything at all...Romaine
std::optional requires the programmer to unpackage it. The compiler won't give a peep if the code blithely dereferences the pointer and didn't bother to ensure it isn't nullptr. But putting a pointer in a std::optional seems like a bad conflation. Better would be to make a smart pointer that disallows (throws) operations on nullptr.Radtke
@Radtke I see, in my case there is no dynamically allocated variable. I am returning a struct inside a map, (which is a class member).Smash
With a std::optional<SomePointer*>, it has three states: has a valid pointer, has a nullptr, has a std::nullopt. Your code carefully returns valid pointer or std::nullopt ... but the caller or code thereafter ought to still be wary of the "never happen" nullptr possibility. (Well, could have a dangling pointer, or a wild pointer, or uninitialized pointer... I'm ignoring those scenarios.)Radtke
For your consideration, return std::optional<std::reference_wrapper<SomePointer>> which avoids the nullptr state entirely, and still has the packaging behavior, and is self-documenting code. (SomePointer is probably poorly named, since I presume it isn't a pointer.)Radtke
C
12

The sole job of std::optional is to extend the type domain by an additional "null" value. Every pointer type T* already has a value considered "null" - nulltpr.

Thus, it's not a good idea to compare those two directly, because they answer different questions. Sometimes it's important to differentiate between "no result" and "null result"1 (which is one of the possible interpretations), sometimes it's not. You should use whichever fits your needs.

Now if the only reason the code returned a pointer was to make use of the implicit pointer nullability, then the proper solution would be to change it to return std::optional<SomePointer> (or perhaps std::optional<std::reference_wrapper<SomePointer>>) instead, but that's not what you asked about.


1 Of course at this point it's also worthwhile to consider something like e.g. struct NoResult {}; using Result = std::variant<NoResult, SomePointer*>; to make it even more explicit.

Citation answered 4/11, 2021 at 11:59 Comment(9)
I would like to ask if is is more costly to use copy of the value with std::optional or a raw pointer?Smash
@MericOzcan In the current code you're calling at in the 2nd example, which is doing an unnecessary lookup. Other than that, optional<T*> should have neglible, if any at all, overhead over T*. There's no copy of the value pointed to in either case.Citation
@MericOzcan At very first you need to consider (mandatory) return value optimisations, meaning the return value is created directly at the location where it should be assigned to. This eliminates copy costs of returning by value. Apart from between std::optional containing a value and raw pointers (or possibly std::unique_ptr!) apply the same arguments as between non-optional object by value or by pointer...Romaine
Yes I know, I am now considering which is better Case 1. => return std::optional<SomeObject> Case 2. => return &SomeObjectSmash
@MericOzcan This entirely depends on your concrete requirements! return &someObject; is illegal if someObject is a local variable (you return a dangling pointer that way!). Returning by value allows allocation of the objects on the stack (well, provided std::optional is implemented using placement-new and explicit destructor call, not sure if mandated by the standard), which can sometimes be preferrable for performance reasons. Returning raw pointers can imply having to delete returned objects explicitly (in such cases a std::unique_ptr usually is preferrable).Romaine
If you need to return already existing objects of which life-time is controlled by another component raw pointers are good choice, avoiding any unnecessary or even faulty copies.Romaine
Sorry for not being more clear, the return value is a not a local variable but a class member and it is also not dynamically allocated. Thats why I asked that question. Thank you for your help.Smash
@MericOzcan Well, then consider the following difference: If you didn't want to be able to return a null/no result value – when would you return by value and when by reference? By value means you get a copy of the object, by reference you still refer to the original one. Same applies for std::optional vs. raw pointer, solely that now you can return no object at all...Romaine
Yes I already understood. Thanks.Smash
S
1

Types document intent.

With std::optional, the explicit type/interface helps you not forget to check for the null case.

There's a lot of code that assumes that raw pointers are non-null, so it's not always clear whether a raw pointer is suppose to have an std::optional<T&> or T& contract.

std::optional cant hold references, so sometimes std::optional<T*> is the the best way to communicate the nullable contract as distinct from the non-null contract.

Also read: T* makes for a poor optional<T&>

Raw pointers are bad because they always force you to use the std::optional<T&> contract even when most of the time you actually only need the T& contract. segfaults everywhere.

Spencer answered 29/10, 2022 at 7:19 Comment(0)
S
0

Expanding on Elo's answer, optional might be helpful in case in which your enum class variable needs to take a empty value. I dont think enum class variables can be NULL.

Scuta answered 29/10, 2022 at 6:41 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewLajuanalake

© 2022 - 2024 — McMap. All rights reserved.