Just Software Solutions

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: , ,
Stumble It! stumbleupon logo | Submit to Reddit reddit logo | Submit to DZone dzone logo

Comment on this post

If you liked this post, why not subscribe to the RSS feed RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.

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.

by Steven Wolf at 15:11:46 on Thursday, 21 March 2019

Naming is hard. A poll on reddit suggested std::pointy_mcpointface<T> (https://strawpoll.com/c4fd88ap)

I still prefer object_ptr<T>.

by Anthony Williams at 15:16:14 on Thursday, 21 March 2019
I agree that observer_ptr<> is a terrible name. I have named my own implementation alias_ptr<> as it clearly describes that it is an alias to an existing object rather than a pointer to an object that the wrapper owns. When I tried to update interfaces in an existing codebase to use this I found the name still a bit long. For this reason, I added two aliases as well: template<typename T> using ptr = alias_ptr<T>; template<typename T> using cptr = alias_ptr<const T>;
by Sam Saariste at 17:04:53 on Monday, 08 April 2019

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 :)

by Aleksey Gurtovoy at 05:49:06 on Thursday, 25 April 2019

Forgot to mention: totally agree about allowing incoming implicit conversions. Someone needs to write a paper to fix `observer_ptr`.

by Aleksey Gurtovoy at 05:49:06 on Thursday, 25 April 2019

"someone needs to write a paper..."

https://github.com/tvaneerd/isocpp/blob/master/observer_ptr.md

by Tony Van Eerd at 07:07:23 on Monday, 29 April 2019

Thanks for the link Tony. Many of the changes vs observer_ptr were inspired by your emails :)

by Anthony Williams at 07:10:07 on Monday, 29 April 2019

Add your comment

Your name:

Email address:

Your comment:

Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy