C++ Tips: 1 - Avoid unnecessary optionality

One of my main aims when writing code in C++ is to have the code clearly communicate its purpose. I find it useful to be able to look at a single line in isolation and have a pretty good idea of what its effects are on the code that it cooperates with. Unfortunately C++ code can often be written in an imprecise way that makes reasoning about what it actually does harder than it needs to be. By increasing the precision of your code writing you can limit what the code could potentially do to just what you want it to do; by doing this it’s then easy to reason about what the code is doing when you read it as it’s clearly and precisely doing only one thing.

One of the most important, and simplest, things that you can do to reduce code complexity and make your code easier to reason about is to avoid unnecessary optionality.

At its simplest, avoiding unnecessary optionality is simply replacing the use of a pointer with that of a reference. A pointer can either point to something, or not, whereas a reference must always refer to something. If you see a pointer in a piece of code you often have to look around a bit to find out if the pointer is valid or if it could be null. If you see a reference you know straight away that it is valid. That’s pretty much all there is to it. If the code you are writing uses an object that is optional then you might choose to use a pointer to represent that optionality, if the object is not optional and must always be present then you should use a reference.

I personally find that reasoning about references when examining code is less complex that reasoning about pointers. A reference can do less that a pointer, it’s more precise because it can only refer to a valid object whereas a pointer can refer to a valid object or null. Each time you see a pointer you have to work out if it is likely to be valid from the context in which you see it and from where you obtained it from, when you see a reference you don’t have to do that. Whilst reading, or working through, complex code that you’re not that familiar with, pointers require that you remember more about a variable than references.

Of course, about now someone will pipe up with a comment that points out that you can subvert the C++ reference mechanism by deliberately creating a reference to a null pointer. That’s true, but if you have programmers that are doing that then you have more fundamental problems that this tip might help you address.

As always, the devil is in the detail. What does it mean to remove unnecessary optionality in practice?

Assume we have a container that maps from string names to widgets. Widget objects are pretty large so we decide to store pointers to widgets in the container and have the container manage the lifetime of the widgets that it contains. This could be a simple stl::map but I tend to prefer a more precise interface so I would tend to write a wrapper class that exposes the interface that I actually need.

Suppose that the usage pattern for the collection of widgets is that they’re loaded at program start up and the names are presented to the user so that they can select an appropriate widget to manipulate. What should the code that retrieves a widget from the collection look like?

Often you’ll see code in the collection like this:

CWidget *CWidgets::Find(
   const std::string &name)
{
   CWidget *pWidget = 0;
   
   Widgets::iterator it = m_widgets.find(name);
   
   if (it != m_widgets.end())
   {
      pWidget = it->second;
   }
   
   return pWidget;
}

and code at the point of use like this:


CWidget *pWidget = m_widgets.Find(name);

assert(pWidget != 0);

DoThisWithWidget(pWidget);
DoThatWithWidget(pWidget);
// more code that manipulates the widget

One of the problems with this kind of code is that it doesn’t clearly express what’s going on. The code says the widgets collection may contain a widget with this name whereas the requirement is that the widgets collection always contains a widget with the supplied name. The collection is slightly more general purpose than it needs to be and because of this we inject a small amount of uncertainty into the code that uses it. Although the code following the assert may be protected from bugs that cause the collection not to contain the expected value the programmer still needs to reason in terms of a pointer, a potentially optional widget, rather than in terms of a reference to a widget that must exist. The slight addition in complexity is negligible at this point since you can easily see that the assert that states the pointer must always be valid, however, once we trace the code into DoThisWithWidget() or DoThatWithWidget(), or even as we move further away from the assertion code, things may, or may not, be more complex and harder to reason about.

My preferred solution to this accidental complexity is to remove the unnecessary optionality. Instead of using CWidgets::Find() as shown above we would use something like this:

CWidget &CWidgets::Get(
   const std::string &name)
{
   Widgets::iterator it = m_widgets.find(name);
   
   if (it == m_widgets.end())
   {
      throw CException("Widget: \"" + name + "\" not found");
   }
   
   return *(it->second);
}

This method on the widget collection will return a widget of the specified name if it exists and throw an exception if it doesn’t. Obviously this is inappropriate if you need to find out if a widget exists in the collection, but it is appropriate if the widget must exist. What is done with the exception is up to the user of the widget collection, or, perhaps, the user of the user of the widget collection.

The code at point of use becomes something like this:


CWidget &widget = m_widgets.Get(name);

DoThisWithWidget(widget);
DoThatWithWidget(widget);
// more code that manipulates the widget

It’s now quite clear that if the code ever returns from the call to Get() then you have a valid widget. Likewise, code following this point is clearly written with the intention of working with a widget that is always present. This allows our removal of optionality to “trickle down” to the functions that follow; DoThisWithWidget() and DoThatWithWidget() can now be written to take a reference and any assertions or checking for valid pointers inside them can be removed. Code such as this:

void DoThisWithWidget(
   CWidget *pWidget)
{
   assert(pWidget != 0);

   // do stuff with widget

or this:

void DoThisWithWidget(
   CWidget *pWidget)
{
   if (!pWidget)
   {
      // handle unexpected error, widget can't be null!
   }
   
   // do stuff with widget

need never exist. Instead we just have code that clearly communicates its expectations in a way that the compiler can confirm and that we, as programmers, can forget.

void DoThisWithWidget(
   CWidget &widget)
{
   // do stuff with widget

Often people will complain that they must use pointers rather than references for non-optional objects for some reason. Most of the time they’re wrong ;) Sure, you may be passed a pointer that can never be null from an API that you’re using but you don’t need to pass this design pollution on to the rest of your code. Likewise you may use an API that requires a pointer but that doesn’t mean you need to pass the object as a pointer within your own code.

Even when an object is optional you may still gain some clarity by removing the optionality. Of course if the object really is optional then you need to fabricate something to use when the optional object isn’t present, for this you can often use the Null Object Pattern. This replaces an optional object which may be nothing with an object that will always be there but may do nothing. Using a Null Object often allows you to move switching based on the presence of the optional object and consolidate the “doing nothing” within the null object rather than spreading it across all the users of the object in question.

Code that clearly communicates what it is doing is easy to understand. Unnecessary optionality adds a small amount of confusion to code by having the code say that it could do one of two things when in fact it only actually does a single thing. Removing unnecessary optionality can make code communicate more clearly and can reduce uncertainty when reading unfamiliar code.