问题
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?
回答1:
Considering that having a weak_ptr
always requires reference counting, rolling out whatever solution is (more or less) like rewriting the shared_ptr
one.
The quick and dirty way is probably derive shared_ptr and provide it with only the move ctor (monitore_ptr(monitored_ptr&&)
) and transfer operator (monitored_ptr& operator=(monitored_ptr&&)
), thus disabling the shared_ptr
copy (and hence "sharing") capabilities.
The problem of derivation is that, being shared_ptr
not polymorphic, you end up with a non polymorphic type that exibit some polymorphism towards shared_ptr (you can assign to it, thus violating your assumptions).
This can be compensated by using protected inheritance and re-expose only the required functionalities (essentially the *
and -
> operators).
To avoid miss-behavior against weak_ptr
(like your monitored_ptr
given to weak_ptr
given to shared_ptr
)... I'll also suggest to override weak_ptr
as well, with protected inheritance.
At that point you end up with a pair of classes that are self sufficient and not compatible with any other shared pointer.
In any case, the key is writing proper contructors, and not (as you proposed) throw in the destructor: it is a situation with a lot of potential gotcha, hardly manageable.
(see for example here)
回答2:
I'm going to propose a pretty simple design. It is a thin wrapper around a weak_ptr
where the only way to access the underlying T
is to pass a lambda to a method.
This restricts the lifetime of the shared_ptr
from lock()
to be the time you call the method: while in theory you can lock the shared_ptr
indefinitely, you can only do it by never returning from the try
.
template<typename T>
struct monitored_pointer {
template<typename Lambda>
bool try( Lambda&& closure ) const {
auto p = m_ptr.lock();
if (!p)
return false;
std::forward<Lambda>(closure)(*p):
return true;
}
bool valid() const {
return try( [](T&){} );
}
void reset( std::weak_ptr<T> ptr = std::weak_ptr<T>() )
{
m_ptr = ptr;
}
explicit operator bool() const { return valid(); }
monitored_pointer() {}
monitored_pointer( monitored_pointer && ) = default;
monitored_pointer& operator=( monitored_pointer && ) = default;
explicit monitored_pointer( std::weak_ptr<T> ptr ):m_ptr(ptr) {}
private:
std::weak_ptr<T> m_ptr;
};
valid
and operator bool
just helps when you want to clean out expired monitored_pointer
s.
Use looks something like:
if (!ptr.try( [&]( Touch& touch ) {
// code that uses the `touch` here
})) {
// code that handles the fact that ptr is no longer valid here
}
来源:https://stackoverflow.com/questions/18525845/how-can-i-enforce-single-ownership-with-weak-ptr-or-how-to-subclass-shared-pt