object_ptr - a safer replacement for raw pointers
Thursday, 21 March 2019
Yesterday I uploaded my object_ptr<T>
implementation
to github under the Boost
Software License.
This is an implementation of a class similar
to
std::experimental::observer_ptr<T>
from
the Library Fundamentals TS 2, but with various
improvements suggested in WG21 email discussions of the feature.
The idea
of
std::experimental::observer_ptr<T>
is
that it provides a pointer-like object that does not own the pointee, and thus
can be used in place of a raw pointer, but does not allow pointer arithmetic or
use of delete
, or pointer arithmetic, so is not as dangerous as a raw
pointer. Its use also serves as documentation: this object is owned elsewhere,
so explicitly check the lifetime of the pointed-to object — there is
nothing to prevent a dangling std::experimental::observer_ptr<T>
.
My
implementation of this concept
has a different name (object_ptr<T>
). I feel that observer_ptr
is a bad
name, because it conjures up the idea of the Observer pattern, but it doesn't
really "observe" anything. I believe object_ptr
is better: it is a pointer to
an object, so doesn't have any array-related functionality such as pointer
arithmetic, but it doesn't tell you anything about ownership.
It also has slightly different semantics to std::experimental::observer_ptr
:
it allows incoming implicit conversions, and drops the release()
member
function. The implicit conversions make it easier to use as a function
parameter, without losing any safety, as you can freely pass a
std::shared_ptr<T>
, std::unique_ptr<T>
, or even a raw pointer to a function
accepting object_ptr<T>
. It makes it much easier to use jss::object_ptr<T>
as a drop-in replacement for T*
in function parameters. There is nothing you
can do with a jss::object_ptr<T>
that you can't do with a T*
, and in fact
there is considerably less that you can do: without explicitly requesting the
stored T*
, you can only use it to access the pointed-to object, or compare it
with other pointers. The same applies with std::shared_ptr<T>
and
std::unique_ptr<T>
: you are reducing functionality, so this is safe, and
reducing typing for safe operations is a good thing.
I strongly recommend using object_ptr<T>
or an equivalent implementation of
the observer_ptr
concept anywhere you have a non-owning raw pointer in your
codebase that points to a single object.
If you have a raw pointer that does own its pointee, then I would strongly
suggest finding a smart pointer class to use as a wrapper to encapsulate that
ownership. For example, std::unique_ptr
or std::shared_ptr
with a custom
deleter might well do the job.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: cplusplus, memory, safety
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy
7 Comments
Hey - maybe 'rented_ptr<>' is a better name? :)
Love the idea - and wholly approve of your ease-of-use and replacement choices. Making a safer / better / improved idiom HARDER to replace existing totally unsafe use cases is a recipe that pretty much guarantees low-to-zero adoption of the improvement - and much resentment towards those who try to impose it on others.
80/20 is the sweet spot. 99/1 makes life miserable and tells your dev team that they're all too stupid and untrustworthy to make their lives reasonably simple.
Naming is hard. A poll on reddit suggested std::pointy_mcpointface<T> (https://strawpoll.com/c4fd88ap)
I still prefer object_ptr<T>.
We've used something very much like this in a large production codebase some 15 years ago (together with the usual set of owning smart pointers), and it greatly improved our level of comfort around non-owning pointers and actually had a noticeable effect on the number of pointer-related issues. Our version (and all the owning pointers as well) also included a nullable/non-nullable flag as a part of the type (ref_ptr<T> vs ref_ptr<T,!0>) and enabled/disabled the corresponding implicit conversions between the nullable/non-nullable pointers accordingly (backed by run-time debug asserts similar to the STL debug mode). A nullable-to-non-nullable cast looked like this: ptr|!null. With all of this we basically obliterated pointer issues in production.
Thanks for the trip down the memory lane :)
Forgot to mention: totally agree about allowing incoming implicit conversions. Someone needs to write a paper to fix `observer_ptr`.
"someone needs to write a paper..."
https://github.com/tvaneerd/isocpp/blob/master/observer_ptr.md
Thanks for the link Tony. Many of the changes vs observer_ptr were inspired by your emails :)