How can I enforce single ownership with weak_ptr? (Or, how to subclass shared_ptr and override dtor)

落爺英雄遲暮 提交于 2019-12-11 18:36:32

问题


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_pointers.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!