Modern C++

Using the not_null Template for Pointers That Must Never Be Null

Null pointer exceptions can get downright annoying, so who needs that?

It's time to meet the Guideline Support Library, a small helper library that provides some missing functionality. I'll show you how to ensure a pointer is never null, which will make your applications both faster and less likely to encounter runtime errors.

One of the most frustrating runtime errors around is the null pointer exception. That's why code is strewn with checks to make sure a pointer isn't null before you try to dereference it. These checks aren't just annoying to write, they slow down your application. Yet if you miss just one, you're at risk of trying to dereference a null pointer, and that never ends well.

Many developers try to use references as much as they can because references can't be null, but you can't re-home a reference and get it to refer to something different, as you can with a pointer. So there are times pointers are what you must use, which brings you back to all that null-checking and the recurring fear of a null pointer exception. To help get you out of that conundrum, guideline I.12 tells you: Declare a pointer that must not be null as not_null.

The reason the guide gives is terrific: To help avoid dereferencing nullptr errors, and to improve performance by avoiding redundant checks for nullptr.

My only issue with this guideline is that it doesn't mention there's no such thing as std::not_null. This incredibly useful template is actually part of the Guidelines Support Library (GSL). One implementation of it is at github.com/Microsoft/GSL. It's a header-only library you can just download (click the Download Zip button, then unzip it somewhere and add the files into your project) and use right away. All its templates are in the gsl namespace.

To see how it can help you, consider a code snippet like this (it doesn't really matter for these purposes what DoSomething does):

Service s(1);
Service* ps = &s;
i = ps->DoSomething();
ps = nullptr;
i = ps->DoSomething();

The first call will be fine, but the second will give a runtime error. To prevent that, you need to check that pointer every time before you use it. Here it's obvious that the pointer is null, but often it isn't. This leads people to write wrapper functions like this:

int AskServiceToDoSomething(Service* p)
{
  if (p)
  {
    return p->DoSomething();
  }
  return -1; //or some other signal value, or an appropriate default like 0
}

Now you can adjust the code to use the wrapper:

Service* ps = &s;
i = AskServiceToDoSomething(ps);
ps = nullptr;
i = AskServiceToDoSomething(ps);

This means no runtime errors, but it's really annoying. So consider this change instead. Include "gsl.h" and then change the declaration of the pointer:

gsl::not_null<Service*> ps = &s;

You don't need to change anything else. The code that dereferences ps doesn't need to change. And the code that passes this value (which isn't really a pointer any more) to AskServiceToDoSomething doesn't need to change, because the template has conversion operators to do all the right things. But something amazing happens when you do this -- your code won't compile. I know … because when is that amazing, right? But it is. The compile error is on this line:

ps = nullptr;

And the error is:

error C2280: 'gsl::not_null<Service *> &gsl::not_null<Service *>::operator =(std::nullptr_t)': 
  attempting to reference a deleted function

The authors of this template included a cool trick that refuses to construct a not_null<Something*> from nullptr, or to process an assignment from nullptr. This means you can get rid of the wrapper function AskServiceToDoSomething and just go back to dereferencing the pointer. It won't be null, and it can't be null.

Of course, it's possible to write code that hides nullness from the compiler. If you do, you'll have a problem at runtime. But -- and this is so important -- you'll be notified of the problem not when the null pointer is used, which could be a long way from the mistaken code that put null into it, but when it is set to null. By default, you'll be notified by the termination of your app, which might be a little drastic. To see it in action, hide the nullptr from the compiler by writing this little function:

Service* GetPointer(Service* p)
{
  return p;
}

and then use that to set ps:

ps = GetPointer(nullptr);

This will cause your application to terminate on the line that calls GetPointer, not the line afterward that tries to dereference it. If you agree that instant termination is a bit strong, you can instruct it to throw an exception instead:

#define GSL_THROW_ON_CONTRACT_VIOLATION 1

This may still end up as an unhandled exception (which means your application will terminate), but you could, if you wanted to, wrap a large function in a try/catch block. That way you could at least catch the exception and manage to provide a slightly dignified error message before terminating the application. What's more important here is that accidental errors, like initializing a pointer to nullptr and assuming one of the control paths will give it a legitimate value, just can't happen when you use the not_null template.

All the dozens of places in your code that use the pointer can remove their checks that make sure the pointer isn't null -- and that's going to speed up your application. And if someone ever does manage to get a null value into one of these pointers, you'll find out what line of code did that -- not what hapless line of code much further down the line tried to use the pointer and found out it was null.

There's more in the GSL beyond not_null, and I encourage you to look into it. The guidelines make a number of references to helpers from the GSL without indicating that's where they're from. So if you're reading a guideline and it tells you to use something you've never heard of, be sure to see if it's part of the GSL. It's a useful library that makes some guidelines possible that would otherwise be impossible.

About the Author

Kate Gregory has been using C++ since before Microsoft had a C++ compiler, and has been paid to program since 1979. Kate runs a small consulting firm in rural Ontario, Canada, and provides mentoring and management consultant services, as well as writing code every week. She has spoken all over the world, written over a dozen books, and helped thousands of developers to be better at what they do. Kate is a Microsoft Regional Director, a Visual C++ MVP, an Imagine Cup judge and mentor, and an active contributor to StackOverflow and StackExchange sites. She develops courses for Pluralsight, primarily on C++ and Visual Studio. Since its founding in 2014 she is the Open Content Chair and speaker for CppCon, the definitive C++ conference.

comments powered by Disqus

Featured

Subscribe on YouTube