Vorac
Vorac

Reputation: 9122

How to manage lifetime of the type "shortest of"?

We use composition, when an object has a single parent, which should care for the object's lifetime. We use unique_ptr when in the same situation, but the object can be nullptr.

We use shared_ptr when several external entities may need our object, so its lifetime is extended until the last of those external entities loses interest.

Here I want to ask about another lifetime situation. What if the object needs to live the shortest of several durations?

Here is an example. Let's have a one-shot timer, which stores a functor and executes it after a the counting is complete. It makes sense to me*, that this timer object is destroyed after:

1. fulfilling its task - therefore it should be able to destroy istelf

 or

2. the parent loosing interest in the timer - so the parent should be able to 
     destroy the object as well

Currently, I using an awkward implementation with unique pointers. What would be a good pattern / guideline / implementation of this problem?

* reasons: 1) the functor could be owning some other resources 2) the timer could have been set to a very large number, and then abandoned 3) if the parent has been destroyed, we generally don't want to invoke its callbacks

Upvotes: 3

Views: 56

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275976

There are serious concurrency and reentrancy problems here.

When two or more bits of code have the right to delete a pointer, neither piece of code can reliably dereference that pointer, as while doing so the other could destroy it.

Similarly, any branch where you check that you have ownership can become stale the moment any other non-local (say a function call) runs, even without concurrency.

We can work around these.

template<class T>
struct shared_destroyable {
  std::shared_ptr<T> lock() const {
    return atomic_load<T>(ptr.get());
  }
  explicit operator bool() const { return (bool)lock; }
  void reset( std::shared_ptr<T> pin = {} ) {
    atomic_store(ptr.get(), std::move(pin));
  }
  shared_destroyable(std::shared_ptr<T> pin):
    ptr(std::make_shared<std::shared_ptr<T>>(std::move(pin))
  {}
  shared_destroyable()=default;
  shared_destroyable(shared_destroyable&&)=default;
  shared_destroyable(shared_destroyable const&)=default;
  shared_destroyable& operator=(shared_destroyable&&)=default;
  shared_destroyable& operator=(shared_destroyable const&)=default;
private:
  std::shared_ptr<std::shared_ptr<T>> ptr;
};

This behaves vague like a weak_ptr shared_ptr hybrid.

If the last one goes away, the object is destroyed.

However, if any of them .reset(), the object is destroyed as soon as the last other code that has lock()ed has ended its scope.

Assignment changes which is referred to.

So at use you do this:

if(auto sp = sd.lock()) {
  // use sp
}

and the sp lifetime is guaranteed to last the scope of the {}, even if someone does a sd.reset() within the block, in another thread, or when you call some other method.

Upvotes: 2

Mark Adelsberger
Mark Adelsberger

Reputation: 45819

I think the best solution is to have a wrapper object with a unique pointer to the actual timer object, and a getter for the timer object that returns null if it has been destroyed.

That way if the timer expires and is destroyed, the caller who hasn't lost interest isn't left holding an apparently-valid-but-actually-invalid pointer to the timer object.

Upvotes: 0

Related Questions