0

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?

Community
  • 1
  • 1
Steven Lu
  • 41,389
  • 58
  • 210
  • 364
  • 1
    You can easily check if a `unique_ptr` contains a valid pointer: http://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool – Yuushi Aug 30 '13 at 05:51
  • Well, I wouldn't be checking the unique_ptr. The design of the system is such that it should support a state where the owning smartptr won't be available to the thing that is doing the checking. This is why the weak_ptrs are so nice. I can just pass them around like candy, and as long as nobody goes and does something unholy with the candies, the party will go on. Don't nobody gotta go around keeping tabs on all of 'em. (the candies are magical beacons that are wired together in hyperspace) – Steven Lu Aug 30 '13 at 05:52
  • What is this `lock()` you speak of? Any why would calling it prevent a `shared_ptr` from deleting its owned resource (assuming the reference count is zero)? – Praetorian Aug 30 '13 at 05:59
  • http://en.cppreference.com/w/cpp/memory/weak_ptr/lock – Steven Lu Aug 30 '13 at 06:00
  • 1
    Is it possible to express your object model with a little pseudo-code? I think you may have some faulty premises. Normally you can use a `unique_ptr` member in the owner object and raw pointers for others to use. You just have to be careful the raw pointers lifetime is nested within the owner object lifetime. – Andrew Tomazos Aug 30 '13 at 06:08
  • _seemed elegant to use the destruction of the object that represents a touch to be the signal that the touch has ended_ that rather seems like a violation of, say, the Single Responsability Principle. – stijn Aug 30 '13 at 06:09
  • @stijn: Your comment is a violation of the Dont Appeal To Stupid Principles Principle (tm) – Andrew Tomazos Aug 30 '13 at 06:10
  • really? I honestly think a destructor should just destruct, not also signal – stijn Aug 30 '13 at 06:11
  • @stijn: I agree, but not on principle. – Andrew Tomazos Aug 30 '13 at 06:11
  • @user1131467 I have updated the OP with some pseudocode that should illustrate the situation. It is a pretty unique situation. – Steven Lu Aug 30 '13 at 06:27
  • I think I just realized that the only way that the InputConsumer could be badly behaved in the way I describe is if it went on to dump the shared_ptrs in an container and just holds on to it forever. This is illogical enough that I should allow it (hopefully that makes sense). So there isn't even a problem at all! – Steven Lu Aug 30 '13 at 06:30
  • I'm not following the details of your model completely, but I get the impression that you are over-engineering it. Ask yourself how you could simplify your design. – Andrew Tomazos Aug 30 '13 at 06:32

2 Answers2

0

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)

Community
  • 1
  • 1
Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
  • That sounds painful. I think as long as I don't do something stupid on the receiving end and only use the weak_ptrs in the way that I have been so far (that is, never retaining the shared_ptr's, only creating them on the stack to read out the data), then everything will go smoothly. – Steven Lu Aug 30 '13 at 07:17
0

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
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524