So, I've done (a small amount) of reading and am aware that unique_ptr
in combination with raw pointers is the pattern to use when modeling unique ownership.
However, I really like the simple and clear concept of using a weak_ptr to check if a value is valid, and then discard the shared_ptr after using it, and this keeps everyone happy (at a slight reference counting performance cost).
My particular problem right now is in creating an expressive and flexible system for tracking multitouch points, and it seemed elegant to use the destruction of the object that represents a touch to be the signal that the touch has ended. If I went with the raw pointer route, I would need to define some semantics that each component interfacing with this system would need to conform to, something slightly ugly like having a second argument involved that indicates whether the pointer is valid or some such. This issue with the raw pointer route is perhaps a strawman issue as I don't expect this to become a large project, but the question is mostly of practical interest in terms of how to write the best modern C++ code.
pseudocode:
class InputConsumer {
void handle(std::list<std::weak_ptr<Touch>>*);
// consumer doesnt hold references to anything outside of its concern.
// It only has to know how to deal with input data made available to it.
// the consumer is a child who is given toys to play with and I am trying to
// see how far I can go to sandbox it
}
class InputSender {
std::list<std::weak_ptr<Touch>> exposedinputdata;
std::list<std::shared_ptr<Touch>> therealownedtouches;
// sender populates exposedinputdata when input events come in.
// I want to let the consumer copy out weak_ptrs as much as it wants,
// but for it to never hold on to it indefinitely. There does not appear
// to be an easy way to enforce this (admittedly it is kind of vague. it
// has to be around for long enough to be used to read out data, but
// not e.g. 3 frames. Maybe what I need is to make an intelligent
// smart pointer that has a timer inside of it.)
std::list<std::weak_ptr<InputConsumer>> consumers;
void feedConsumersWithInput() {
for (auto i = consumers.begin(); i != consumers.end(); ++i) {
if (i->expired()) {
consumers.erase(i);
} else {
i->lock()->handle(&exposedinputdata);
}
}
}
When I saw the ability of weak_ptr
to express very similar semantics to what I am modeling, I just really wanted to use it, because its interface is clean and simple, and most importantly it self-documents how this code is going to work. This is a huge benefit down the road.
Now I'm pretty sure that everything will be really peachy until such time as an InputConsumer
calls lock()
on weak_ptr<Touch>
and retains the shared_ptr<Touch>
. It will prevent the underlying Touch
from being freed even after the primary owner of it has erased its owning shared_ptr
! This seems to me the only wrinkle, and a little one at that. I think it's far harder to screw up ownership handling with shared_ptr than it is to do so with raw pointers.
What are some ways of patching this up? I am thinking of maybe making a template subclass (?! I have never written such a thing, recently got into templates. Loving them) of weak_ptr that will somehow forbid retaining a shared_ptr, or something.
Maybe I can subclass shared_ptr
and override its dtor to throw if it doesn't call the deleter?