I recently hit an issue where neither unique_ptr
nor shared_ptr
seemed like the right solution.
So, I am considering inventing another kind of smart ptr (described below), but I thought to myself "surely I am not the first to want this."
So my high-level questions are:
std::
features), perhaps I am missing something?Requirements:
unique_ptr
shared_ptr
's behavior).weak_ptr
, but to be used with a single ownership model.Motivating example:
Suppose I am iterating a list of interface pointers, calling methods on them. Some of those methods may result in items later in the list being deleted.
With plain pointers, I would get dangling references for those deleted items.
Proposed design:
Let's call the owning pointer my_ptr
and the non-owning reference my_weak_ptr
.
For a given object, we might have a diagram like this:
_______
my_ptr<Obj> owner ---------> |Obj* | -------> [Obj data ... ]
+----> |count|
| +--> |_____|
my_weak_ptr<Obj> A ---+ |
|
my_weak_ptr<Obj> B -----+
my_ptr
would have an interface largely identical to unique_ptr
.
Internally, it would store a pointer to a "control block" which is really just the "real" pointer and a refcount for the control block itself.
On destruction, my_ptr
would set the control block pointer to NULL and decrement the refcount (and delete the control block, if appropriate).
my_weak_ptr
would be copyable, and have some get()
method which would return the real Obj*
.
The user would be responsible for checking this for NULL before using it.
On destruction, my_weak_ptr
would decrement the count (and delete the control block, if appropriate).
The downside is doing two hops through memory for each access.
For my_ptr
, this could be mitigated by storing the true Obj*
internally as well, but the my_weak_ptr
references will always have to pay that double-hop cost.
Edit: Some related questions, from links given:
So it seems like there is demand for something like this, but no slam-dunk solutions. If thread safety is needed, shared_ptr
and weak_ptr
are the right choices, but if not, they add unnecessary overhead.
There is also boost::local_scoped_ptr
, but it is still a shared ownership model; I'd rather prevent copies of the owning pointer, like unique_ptr
.
There was some good discussion in the comments above, so I'll try to answer my own question and summarize:
First, there is an overall downside to the whole concept: Any user of my_weak_ptr
needs be very careful not to call some function which could result in the underlying object being deleted. Or if they do, they need to re-check the weak ptr for nullness. This is an unenforced (and unenforceable) constraint placed on the user, the same as if they were using raw pointers.
That being said: this is not new territory. In subsequent research, I have found various incarnations of such an idea:
WeakPtr
has no lock()
method, and the docs say "Weak pointers become magically null if the referred object is destroyed from elsewhere."T
must be wrapped as trackable<T>
, but similar problem being solved.There are also some "pretty good but not quite ideal" solutions closer to std
:
shared_ptr
and weak_ptr
.
weak_ptr
.
Probably local_shared_ptr
is the best out-of-the-box solution, with high quality and few downsides.
However, to really squeeze out the last few bytes, and to disallow copying, a custom solution would be needed.
Aside, more philosophically:
I get the sense, both from discussion here and other reading, that many believe in a binary approach toward ownership: either it is shared (so use shared_ptr
, which also gives you shared observation via weak_ptr
) or it is unique (so use unique_ptr
).
That probably covers a good 90%+ of cases. Yet I want unique ownership with shared observation (that's my phrasing; you might use different words depending on your semantics). Probably too corner-case to be covered by the standard, but I think it seems like a reasonable niche, for resource-constrained systems.
There is a design which does not allocate extra block, but instead it makes pointer object sized as 3 pointers. A pointer is a node of double-linked list, each weak reference is a node of the same list.
Drawbacks are linear deletion complexity (must nullify each reference), and infeasibility making this efficiently thread safe.
Advantage is fast dereference of both shared and weak pointers.
I don't recall where exactly I've seen or heard this idea...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With