Non-nullable pointers with 0-overhead and no hidden runtime cost.
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 likestd::unique_ptr
, and does not require runtime checks unless explicitly
specified.
Stop worrying about nulls, and use not_null
today!
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)));
unique_ptr
, and still allows nullabilitynot_null
has no hidden additional runtime
or storage overhead.
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:
nullptr
, which risks thenullptr
before using — resulting in more complicated codeThis 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 aassume_not_null
, which does no checking to produce the not_null
and hasWith 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());
}
The defaults for this library should satisfy most consumers needs; however
not_null supports two optional features that may be controlled
through preprocessor symbols:
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;
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;
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:
std::abort
, causing immediate terminationstderr
to allow context for thenot_null is compatible with any compiler capable of compiling valid
C++11. Specifically, this has been tested and is known to work
with:
Latest patch level releases are assumed in the versions listed above.
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.
alloy:
:not_null
:gsl::not_null
:doxygen:
:nn
: