Representing missing value in C++

One thing that I have bothered me quite a lot when coding in C# is a good way to indicate the absence of a value. There are a few different alternatives how this can be done, the most common being using null. However, using null instead of an actual type quite often leads to crashes and bugs due to NullReferenceExceptions being thrown when a null check is forgotten somewhere in the code.

string GetName(int personId)
{
    var p = repository.Find(personId);
    return p.FirstName + " " + p.LastName; // What if p is null?
}

string GetNameWithNullCheck(int personId)
{
    var p = repository.Find(personId);
    if (p == null) return null; // The caller needs to check if null is returned
    return p.FirstName + " " + p.LastName;
}

As can be seen in the code above it is easy to forget to check for null and there is nothing in the last method that informs the caller that he GetNameWithNullCheck might return null.

Functional languages like Haskell and (functional first) F# have a different approach to representing the absence of a value; Maybe in Haskell and Option in F#. These can both be seen as a container that can either be empty or contain a single value. As you might have guessed, an empty container represents the absence of a value.

let getName personId =
    match find(personId) with
    | Some(p) -> Some(p.firstName + " " + p.lastName)
    | None -> None

In the code above, both find and getName returns an Option. By matching against Some and None it can be determined if find returned a value and act accordingly. The big difference here, comparing to the C# methods, is that the returned value must be checked if it has a value or the code will not even compile. Compile errors should always be preferred to runtime errors.

With this covered, lets investigate how the case of a missing value can be handled in the C++ language. The old way of handling the absence of value was probably to use pointers and in the case of no value, a nullptr. This is by no means better than null in C#. Dereferencing a nullptr can lead to all kinds of errors in an application and should be avoided at all costs. This is why it was happily surprised when I discovered that an optional type had been added to the C++ standard library as of C++17.

#include <optional>

std::optional<std::string> getName(int personId) {
    auto p = repository.find(personId);
    if (p)
        return p.getFirstName() + " " + p.getLastName();
    return std::nullopt;
}

Using optional has one major advantage over the other ways of handling a missing value; it is absolutely clear just by looking at the function’s signature that it may or may not return a value. You do not need to spend any time reading and understanding the function’s body to understand this.

The caller of the getName function will have to check whether the returned optional contains a value or not. This can be done in a couple of different ways depending on what fits best for the situation.

// n1 is assigned the name of the person or "No name" if there is no person with id 123
auto n1 = getName(123).value_or("No name");
// prints a greeting if a person with id 234 exists
auto o1 = getName(234);
if (o1)
    std::cout << "Hello " << *o1 << std::endl;
// throws an exception if no person with id 345 exists
auto o2 = getName(345);
if (!o2.has_value())
    throw std::invalid_argument("Invalid user id provided");

To conclude, whenever there is a place in the code that may or may not have a value, prefer using optional to clearly indicate that this is the case and help avoiding misunderstandings and bugs.

Lämna en kommentar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *