4

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) ?

Meric Ozcan
  • 678
  • 5
  • 25
  • 4
    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. – elo Nov 04 '21 at 11:38
  • Case 2 has more nulls, it supports both `nullptr` and `std::nullopt`, more choice :). It's more usual to use `std::optional` without the `*` in there. –  Nov 04 '21 at 11:42
  • Why should we return copy? Instead of a pointer? @dratenik – Meric Ozcan Nov 04 '21 at 11:51
  • 1
    That `const_cast` is _extremely_ sketchy, by the way. – Bartek Banachewicz Nov 04 '21 at 12:00
  • `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... – Aconcagua Nov 04 '21 at 12:10
  • `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`. – Eljay Nov 04 '21 at 12:42
  • @Eljay I see, in my case there is no dynamically allocated variable. I am returning a struct inside a map, (which is a class member). – Meric Ozcan Nov 04 '21 at 12:48
  • 2
    With a `std::optional`, 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.) – Eljay Nov 04 '21 at 12:55
  • 3
    For your consideration, return `std::optional>` 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.) – Eljay Nov 04 '21 at 13:01

3 Answers3

8

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.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
  • I would like to ask if is is more costly to use copy of the value with std::optional or a raw pointer? – Meric Ozcan Nov 04 '21 at 12:11
  • 1
    @MericOzcan In the current code you're calling `at` in the 2nd example, which is doing an unnecessary lookup. Other than that, `optional` should have neglible, if any at all, overhead over `T*`. There's no copy of the value pointed to in either case. – Bartek Banachewicz Nov 04 '21 at 12:13
  • 1
    @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... – Aconcagua Nov 04 '21 at 12:15
  • Yes I know, I am now considering which is better **Case 1.** => return std::optional **Case 2.** => return &SomeObject – Meric Ozcan Nov 04 '21 at 12:15
  • 1
    @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). – Aconcagua Nov 04 '21 at 12:20
  • 1
    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. – Aconcagua Nov 04 '21 at 12:20
  • 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. – Meric Ozcan Nov 04 '21 at 12:23
  • 2
    @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... – Aconcagua Nov 04 '21 at 12:27
  • Yes I already understood. Thanks. – Meric Ozcan Nov 04 '21 at 12:31
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.

Tom Huntington
  • 2,260
  • 10
  • 20
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.

  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/33057455) – Usitha Indeewara Nov 02 '22 at 15:53