Planned Obsolescence of the Pointer
std::unique_ptr are the foundation on which
all things living on the heap are build (at least since
C++11). And it
is a stable foundation. Rarely, there is a good reason for not using a
smart pointer to manage an allocated object. For instance,
std::unique_ptr comes with close to zero overhead.
One may come to the conclusion that the committee wants to hold off users from having to use pointers in any form. We have an alternative to raw pointers for almost all of their classical use cases:
Obviously, pointers are needed to hold objects on the heap. If you want runtime polymorphism, there is no way around it. But we have smart pointers. These clearly express the ownership.
If you need variable-length arrays, then you may be tempted to invoke
new. But wait,
std::vector is perhaps the most widely used
C++. It is straight-forward to use and superb in terms
Containers And Polymorphism
Okay, but how about runtime polymorphism plus containers? That’s where
we need a
std::vector<T*>, correct? The smarter alternative
is boost’s pointer containers. Although, I have never grown a
huge fan of them. But once more: pointers are not necessary here!
Obviously, we have
std::vector<std::unique_ptr<T>> and if you want
the same thing with references, there is
works as expected with standard containers.
Exhibit A: Usage of
std::reference_wrapper in a STL container.
Finally, we can use a raw pointer to represent either a reference to a
value (obtained by dereferencing the pointer) or the absence of value
std::nullptr in which case dereferencing the
pointer will remind of you Tony Hoare’s apology for
his billion-dollar mistake).
A reference or nothing, sounds familiar? I pointed out previously
the similarities between pointers and
std::optional<T> actually means ‘value’ or ‘nothing’, while we want
‘reference’ or ‘nothing’. This could be represented by an optional
std::optional<T&>). While optional references did
not make it into
std::optional<std::reference_wrapper<T>> seems to be an option
Raw Pointers Be Gone
C++ strives to become more novice friendly, I am inclined to say
with respect to raw pointers, the less the better. In controlled
environments, arcane pointer arithmetic can still be used to achieve
top performance. I am thinking of a heavy-duty class that operates on
pointers in its implementation.
Raw pointers are powerful, but dangerous. The downside of being powerful (or here expressive) is ambiguity. As shown in the use cases above, pointers can mean a lot of things and therefore when you see a pointer in an API, the meaning of the raw pointer is not expressed clearly.
For instance, one item of the Bloomberg coding standard I am not
very fond of is the idea that return values of functions are passed as
pointers to the function (i.e., we distinguish between input and
output parameters of a function). The actual return value often is an
int which indicates success or yields an error code. For example:
This is old-school
C-style and does not have much to do with modern
C++ style, but prior to
C++11 it made some sense.
- Convenience: Prior to
C++11, we did not have
std::tie. Emulating these two facilities using custom classes is much more effort.
- Allocation: By passing a pointer to a function the responsibility to allocate the result remains in the client code and is not dictated to the client code by the function.
- Ambiguity: When the result is not optional, but mandatory, why don’t we pass a reference?
- Uncertainty: Handing out pointers to foreign code in general is worrisome. Will it take ownership of it? Will it attempt pointer arithmetic?
- Interface Clarity: What about this other pointer argument which is supposed to be an input array?
- Allocation: The allocation pro argument is actually a pretty strong one with respect to performance. But how about classes that are part of the function’s library and that are not supposed to live on the stack? It would be better if the library function forces correct usage of the result value upon us.
We have alternative to raw pointers, let’s use them. By using the alternatives we always clearly express our intent and reduce the chances of accidental bugs.
If we want to avoid raw pointers, we have to avoid direct allocation. This brings us to a recommendation by Herb Sutter:
Avoid using plain new or other raw unmanaged allocation directly.
And it brings us to the original motivation for this blog post which
is discussed in the follow-up article:
two strikes against
STL C++ C++11