Optional value is a value that can or can not exist. There are a plenty of examples of optional values: the person’s middle name (not everybody has it) the minimum value of vector (which doesn’t exist when the vector is empty), or the last unprocessed command from a queue.

The boost::optional library is designed for handling such situation in a nice way.

I will present the possible implementations to deal with optional values first without and then with the use of boost::optional. The class presented in the example is similar to one I’ve worked with in a real project. The Pool class is a container for objects Object, which can represent the commands that should be processed. The important Object from the Pool is always the last one, so the Pool interface should provide the method to get the last object in container: getLastObject. Because of the small size of Object, we will accept that it’s returned by value.

struct Object
{
    Object() : m_value(0) {}
    int m_value;
};
class Pool
{
public:
    Pool(){}

    Object getLastObject() // 1
    {
        return m_data.back(); // Oops! Vector can be empty.
    }

private:
    std::vector<Obj> m_data;
};

The problems are coming with the implementation of getLastObject. We need to ensure correct behaviour in case the vector is empty. But what should be returned then?

Without boost::optional

Idea 1. Call getLastObj carefully [anti-pattern!]

Implement Pool::isEmpty method. Call getLastObject only if the Pool is not empty.

if (!pool.isEmpty())
{
    object = pool.getLastObject();
}
else
{
    std::cout << "The pool was empty!"
}

If you think this is the worst possible implementation, you are right. The interfaces should be fault-tolerant. This isn’t. It can result in undefined behaviour when you forget to make the proper check or somebody else try to use it without knowing the limitations.

This implementation is a clear violation of the most important design guideline according to Scott Meyers:

Make interfaces easy to use correctly and hard to use incorrectly.

Don’t look at this awful anti-pattern anymore and let’s search for valid solutions.

Idea 2. Return invalid-object value

If the collection is empty, return specific invalid-value for the Object.

Object Pool::getLastObject() // 2
{
    if (!m_data.empty())
    {
        return m_data.back();
    }
    else
    {
        return Object();
    }
}

It may be the default value or any other value (negative, extreme…), as long as it is impossible for normal existing Object. Otherwise, you won’t be able to distinguish the fake-Objects from real Object that must be processed.

Pool pool;
Object object = pool.getLastObject();
std::cout << "The last Object's value is: " << obj.m_value << std::endl;
// The last Object's value is: 0
// But wait, Pool was empty...

Such code can be misleading — it’s easy to forget to check if the value of Object is valid or not and treat the fake Object as a real one. Also, it’s not always possible to use such implementation. Some objects require a full range of values and for them there is none specific not-existing-object value.

There is also performance drawback of this approach – we need to call the constructor although we don’t really need this fake-Object. We made assumptions that the Object is small and the construction is cheap, anyway here the call is totally redundant.

Idea 3. Return bool value

The return value can indicate whether the Object value is valid or not. The Object is given by reference to function.

bool Pool::getLastObject(Object& object) // 3
{
    if (!m_data.empty())
    {
       object = m_data.back();
       return true;
    }
    else
    {
       return false;
    }
}
Pool pool;
Object object;
bool retVal = pool.getLastObject(object);
if (retVal)
{
    std::cout << "The last value is: " << object.m_value << std::endl;
}
else
{
    std:: cout << "The pool was empty!" << std::endl;
}

This solution can be sufficient in many cases, but it’s not perfect still. When the bool retValue is separated from the Object (for example Object is passed to other function, where the bool retValue is not accessible), there is no possible way to tell if the Object was valid or not. Probably it will be better idea to have the information coupled the Object object. This effect can be achieved in few ways: use the std::pair of bool and Object, add the bool member to Object structure or handle with pointers to Object, where nullptr will stand for not existing value.

Idea 4. Use nullptr for not existing object

Instead of the vector of Objects, the Pool will store the vector of pointers to Objects.

class Pool
{
public:
    Pool(){}

    Object* getLastObject() // 4
    {
        if (!m_data.empty())
        {
            return m_data.back();
        }
        else
        {
            return nullptr;
        }
    }

private:
    std::vector<Object*> m_data;
};
Pool pool;
Object* object = pool.getLastObject();

if (object != nullptr)
{
    std::cout << "The last value is: " << object->m_value << std::endl;
}
else
{
    std::cout << "The pool was empty!" << std::endl;
}

There is no possibility to mismatch nullptr with existing object, you can still use the full range of int m_value member and you didn’t need to add the additional member to Object structure. The unnecessary object construction is also avoided. Anyway, using raw pointers sounds like asking for trouble. You need to take care of pointers deallocation and deal with null references. If you need more arguments why this approach should not be recommended, Sir Charles Antony Richard Hoare calling the null references “A billion dollar mistake” should convince you (full presentation).

(…) I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. - Sir Charles Antony Richard Hoare

A lot of various ideas, but all of them have drawbacks. Now let’s take a look on boost::optional!

With boost::optional

Setup

To include optional from boost, add:

#include <boost/optional/optional.hpp>
using boost::optional;

The library is also a part of std::experimental, so other possibility to include is:

#include <experimental/optional>
using std::experimental::optional;

Usage

Class template boost::optional is a wrapper for object that can have or have not a valid value. The construction of the optional Object looks as follows:

Object someObject;
optional<Object> object_initialized = someObject;
// copy constructor of `Object` invoked

optional<Object> object_uninitialized;
// doesn't call the `Object` constructor

For the uninitialized optional<Object> the Object constructor is not called.

You can check anytime whether the object is initialized or not. This is done by either is_initialized function or by simple putting the object into conditional expression. The optional<Object> will convert to bool.

if (object) // equal to object.is_initialized
{
    // The object was initialized.
}
else
{
    // The object was not initialized.
}

The implementation of Pool::getLastObject with the use of boost::optional is presented below. The return type is optional<Object>. If the vector was not empty, the optional<Object> is initialized with proper value. Otherwise the empty object of type optional<Object> is returned.

optional<Object> Pool::getLastObject() // 5
{
    if (!m_data.empty())
    {
        return m_data.back();
    }
    else
    {
        return optional<Object>{};
    }
}

The code above has the equivalent shorter form:

optional<Object> Pool::getLastObject() // 6
{
    return !m_data.empty() ? m_data.back() : optional<Object>{};
}

Now we can get the last object from Pool with the following code. The m_value is accessed from optional<Object> with the -> operator.

Pool pool;
optional<Object> object = pool.getLastObject();
if (object)
{
    std::cout << "The last value is: " << object->m_value << std::endl;
}
else
{
    std:: cout << "The pool was empty!" << std::endl;
}

You could also use get() method to receive from optional<Object> the instance of type Object.

std::cout << "The last value is: " << object.get().m_value << std::endl;

If the object was uninitialized, calling the get() method or the ->, * operators will result in the assertion.

Conditional constructor

There is also another way to construct optional objects with the use of special two-arguments constructor: optional<T>{condition, value}. The first argument is a condition. The second argument is an initialization value to be used when the condition is true. When the condition is false the object stays uninitialized.

This constructor is useful when the initialization value already exist or it’s built-in type, like int:

optional<int> getValue()
{
    return optional<int>{condition == true, 5};
}

Summary

  • Boost.optional is designed for the values that can be initialized as well as uninitialized and both situations are normal,

  • Boost.optional doesn’t set limitation on the possible values that the object can use,

  • Boost.optional is safer than the usage of nullptr for missing value,

  • Boost.optional doesn’t call the wrapped type constructor for uninitialized values,

  • Boost.optional provides easily accessible and direct information about the object state (initialized/uninitialized),

  • Boost.optional makes your code good-looking, safe and easy to debug.

Further reading