Skip to content

Non-nullable pointers with 0-overhead and no hidden runtime cost.

License

Notifications You must be signed in to change notification settings

bitwizeshift/not_null

Repository files navigation

not_null

Ubuntu Build Status macOS Build Status Windows Build Status Coverage Status Github Issues
Github Releases Bintray Releases
Try online

not_null is a 0-overhead modern utility for ensuring non-nullability in a simple and coherent way.

Unlike gsl::not_null, this type can work with move-only pointers like std::unique_ptr, and does not require runtime checks unless explicitly specified.

Stop worrying about nulls, and use not_null today!

Teaser

auto register_widget(cpp::not_null<std::unique_ptr<Widget>> p) -> void
{
  legacy_service.adopt_widget(std::move(p).as_nullable());
}

...

// use 'cpp::check_not_null' to check and validate that 'p' is not null
register_widget(cpp::check_not_null(std::move(p)));

Live Example

Features

  • Works with unique_ptr, and still allows nullability
  • Zero-Overhead; not_null has no hidden additional runtime or storage overhead.
  • Written to be compatible with C++11
  • Single-header, header-only solution -- easily drops into any project
  • No dependencies

Documentation

  • Background
    A background on the problem not_null solves
  • Tutorial
    A quick pocket-guide to using not_null
  • Installation
    For a quick guide on how to install/use this in other projects
  • API Reference
    For doxygen-generated API information
  • Attribution
    Information about how to attribute this project

Background

When working in a codebase, it's often difficult to know when any given pointer may legally contain a null pointer value. This requires the user to do one of two possible things:

  • Make an assumption that the pointer can never be nullptr, which risks the posibility of a null-pointer dereference leading to undefined behavior, or
  • Always check for nullptr before using -- resulting in more complicated code paths, more branches, and potential pessimizations

This is the driving force behind a not_null utility. The Guidelines Support Library offers gsl::not_null to attempt to satisfy this, though this has two notable limitations:

  • Construction and assignment of not_null is implicit, and requires runtime checks unconditionally (which may result in a contract violation); for example:

    // Performs check that the pointer is not null, when the expression can never
    // take on the form of a non-null pointer
    gsl::not_null<int*> p = new int{42};
  • it does not work with move-only pointer types like unique_ptr, for example:

    gsl::not_null<std::unique_ptr<int>> p = std::make_unique<int>(42);
    // This errors due to 'p' not being movable!
    gsl::not_null<std::unique_ptr<int>> q = std::move(p);

This is where this library comes in.

Rather than allow implicit construction and assignment from the underlying pointer, this library takes the hard stance of correct on construction, which uses factory functions to produce the not_null objects. Two factories are presented:

  • check_not_null, which checks a pointer for nullability before raising a contract violation, which by default is thrown as an exception, or
  • assume_not_null, which does no checking to produce the not_null and has zero overhead, which is to be used for known non-null values

With these, consumers are required to consent to a check, or explicitly state that their pointers are non-null, to reduce errors. This leaves any remaining uses of not_null as nothing more than a 0-overhead wrapper; making any runtime checks a manully consented operation. For example:

// No check performed, since the expression can never be null!
cpp::not_null<int*> p = cpp::assume_not_null(new int{42});

// Get pointer from API that we *expect* to be null, but cannot guarantee
auto q = cpp::check_not_null(get_pointer());

Additionally, not_null allows for move-constructors and move-assignment, since reusing an object after a move is generally a logic-error, and there are many pieces of tooling that will check for such a case. This allows better interop with types like unique_ptr, and also allows for this not_null to communicate with more legacy APIs by propagating such pointers out efficiently:

// Should never be null, but not yet refactored to be 'not_null'
auto old_api(std::unique_ptr<Widget> p) -> void;

auto new_api(cpp::not_null<std::unique_ptr<Widget>> p) -> void
{
  // Extract the move-only unique_ptr, and push along to 'old_api'
  old_api(std::move(p).as_nullable());
}

Optional Features

The defaults for this library should satisfy most consumers needs; however not_null supports two optional features that may be controlled through preprocessor symbols:

  1. Using a custom namespace, and
  2. Disabling all exceptions

Using a Custom Namespace

The namespace that not_null is defined in is configurable. By default, it is defined in namespace cpp; however this can be toggled by defining the preprocessor token NOT_NULL_NAMESPACE to be the name of the desired namespace.

This could be done either through a #define preprocessor directive prior to inclusion:

#define NOT_NULL_NAMESPACE example
#include <not_null.hpp>

auto test(example::not_null<int*> p) -> void;

Try Online

Or it could also be defined using a compiler flag with -D or /D, such as:

g++ -std=c++11 -DNOT_NULL_NAMESPACE=example test.cpp

#include <not_null.hpp>

auto test(example::not_null<int*> p) -> void;

Try Online

Disabling Exceptions

By default, not_null will throw a cpp::not_null_contract_violation when the contract has been violated (during factory construction).

However, it is realized that not all developers appreciate or use exceptions, and that environments that disable exception handling specifically may error due to the existence of a throw expression. To account for this possibility, exceptions may be disabled through the introduction of a simple macro preprocessor symbol: NOT_NULL_DISABLE_EXCEPTIONS.

In doing so, contract-violations will now behave differently:

  • Contract violations will call std::abort, causing immediate termination (and often, core-dumps for diagnostic purposes)
  • Contract violations will print directly to stderr to allow context for the termination
  • Since exceptions are disabled, there is no way to perform a proper stack unwinding -- so destructors will not be run. There is simply no way to allow for proper RAII cleanup without exceptions in this case.

Try Online

Compiler Compatibility

not_null is compatible with any compiler capable of compiling valid C++11. Specifically, this has been tested and is known to work with:

  • GCC 5, 6, 7, 8, 9, 10
  • Clang 3.5, 3.6, 3.7, 3.8, 3.9, 4, 5, 6, 7, 8, 9, 10, 11
  • Apple Clang (Xcode) 10.3, 11.2, 11.3, 12.3
  • Visual Studio 2015, 2017, 2019

Latest patch level releases are assumed in the versions listed above.

License

not_null is licensed under the MIT License:

Copyright © 2019 Matthew Rodusek

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

References