The Discovery of a new useful function

While introducing the swap function we found it handy to modify the already existing private member function destructValue in a ways which made it even more useful:

template <typename T>
class optional {
 // ...
 private:
 // ...
  void destructValue() {
    reinterpret_cast<T*>(&mBuffer.mStorage)->~T();
    mHasValue = false;
  }
}

This function now does a bit more than “just” destroying the value stored in the optional – it also sets mHasValue back to false. This means that it takes an optional with a value and transforms it in an optional without a value.

This is actually a quite helpful operation to have. We can currently already do something like by assigning an empty optional to the optional we want to be empty.

optional<int> mValue(5);
REQUIRE(mValue);
mValue = optional<int>();
REQUIRE(not mValue);

This solution works but it is a bit cumbersome and also requires us to instantiate a whole second option just to do the simple operation which destrcutValue basically already implements.

If we consider constructing an optional with a value to set it’s value, we could also consider the operation to remove said value from the optional to reset the optional. reset seems to be a reasonable name for such a function – this is also the name chosen in the C++ standard.

Tests for reset

There are quite obviously two conditions which can occur when calling such a reset function:

  1. the optional either has a value
  2. or it hasn’t.

In both cases we’d expect that after calling reset() the optional has no value anymore.

TEST_CASE("reset destroys the value of an optional with a value.") {
  int anyValueX = 5;
  optional<int> mValue(anyValueX);
  REQUIRE(mValue);
  mValue.reset();
  REQUIRE(not mValue);
}

TEST_CASE(
    "After calling reset an optional without a value has still a value.") {
  optional<int> mValue;
  REQUIRE(not mValue);
  mValue.reset();
  REQUIRE(not mValue);
}

The Implementation

After having a closer look at our test cases an destrcutValue we will quickly realize that reset does a bit more than destructValue. destrcutValue has a narrow contract: it requires mStorage to actually contain a value of type T. Otherwise calling ~T on this memory location is undefined behavior. If we’d just rename destructValue to reset our second test case wouldn’t work. Therefore we need to add an additional check before calling destructValue.

template <typename T>
class optional {
 // ...
  void reset() {
    if (mHasValue) {
      destructValue();
    }
  }
 // ...
}

With this implementation our test cases will pass and we’ll never invoke undefined behavior.

Conclusion

Based on a previously enhanced private member function we now can effortlessly reset and optional calling reset. In a previous post we already talked about pointer’s being also some kind of optional and used this analogy to motivate the introduction of the pointer syntax to optional. At the time we only considered raw pointer (like they have already been present in C), but there are (since C++11) smart pointers in the C++ standard like std::shared_ptr or std::unique_ptr and (even earlier) boost::shared_ptr.

All of these smart pointer implement a reset function:

All of them (if no argument is supplied) reset the value of the pointer – set the pointer to the NULL pointer and destroys the value (at least potentially). This gives us another reason to have a function called reset within our optional: to not only be consistent with raw pointers but with smart pointers, too.