Down with pointers
Some years ago, there was an Aprils fool post from various other C++ bloggers that C++ has deprecated pointers (for example, Fluent C++ - C++ Will No Longer Have Pointers. Well, as C++ nearly never deprecates anything, that alone was unbelievable. However, do we still need pointers? That's the question I want to cover in today's post.
What pointers say
In C++, pointers signal that a parameter may have a value, or not. Whenever a function receives a pointer, we should write a check in the body whether the parameter is a nullptr
. Sadly, I already saw many cases where this check was omitted. All documentation and comments like a valid non-null object is required do not help and don't make that check obsolete.
I also have seen cases where the nullptr
check on a function parameter was omitted because it was hard to decide what to do in the circumstance of a nullptr
. Say a function that returns void
but has received a nullptr
.
The other interesting part is that this check comes with costs. The compiler, at least from what I have seen, is not able to optimize such a check away, even in a small program. See below for more details.
Use references instead of pointers
This necessity for a check, and the endless comments, go away once we switch to a reference. In contrast to a pointer, a reference expresses that a valid object is required at this point.
A simple approach is to still receive pointers on API boundaries if, for example, you can't change the API. But then, first thing in that function, do the nullptr
-check, return if the pointer is null
. If it is valid, dereference the pointer and store it in a reference.
1 2 3 4 5 6 7 8 |
|
That way, we can at least keep the internal API and code clean. Maybe with the next release, we will get a chance to clean up the public API as well.
Wait, I need a maybe parameter
Okay, then let's change all pointers to references. But what if I need such a maybe parameter? Hm, with maybe you mean optional? Right! For that case, C++17 brings us std::optional
. So please stop abusing pointers when you want to express that the parameter is optional. No need to convert an int
into an int*
just to have the value nullptr
available for comparison.
1 2 3 4 5 6 |
|
The datatype std::optional
is so much better than a pointer. With functions like get_value_or
, it spares us writing an annoying if
which adjusts the value to the stored one or the default.
Okay, but what is with, say, an array? Say we want to pass an array to a function there, we can't use references, except if we make it a template. Oh, and please don't say std::array
because I want this function to be callable with various different array sizes. There I still need a pointer! Got you!
1 2 3 4 5 6 7 8 9 10 11 |
|
span
and string_view
to the rescue
Well, no. At least we don't need a pointer in the API of the function. C++20 brings us std::span
for cases where we want to pass an array or a contiguous container (in this example here, we might also use std::string_view
from C++17). The advantage of std::span
is that it carries the number of elements of the data. So no additional size parameter and way fewer sizeof
's.
1 2 3 4 5 6 7 8 9 10 11 |
|
I think we are at a stage where we can say that there is no need for a pointer for a top-level API anymore. With helper types like std::optional
and std::span
, we can do much better. And yes, pointers are still a thing in C++ and should be. For example, std::span
takes and returns a pointer.
Why do I care that much?
Well, I like clean and expressive APIs. What I also like is efficient code. Have a look at the following example on Compiler Explorer and see for yourself godbolt.org/z/T6qq5q3Tb. You see a full program, including main
. The function Fun
that takes a pointer and checks for nullptr
consumes 7 instructions with -O3
. The version without a check, as well as the reference version, only consumes 3 instructions. This is for a case where the compiler sees the entire program! The interesting part is Opt
. Here I use a std::optional
together with get_value_or
. So essentially, the value is checked. However, both Clang and GCC manage to compile that function to 6 lines of assembly. Not bad, right? Okay, the library part is missing here, so we get some additional costs for the optional
itself.
Do we still need pointers?
Well, I hope I showed you that we at least need them less frequently than we used to. Pointers are still an essential part of C++, but we can use better data types in a lot of places.
Andreas