-2

I have a std::multiset of objects (created in place with set::emplace). This gives me sorted access using a custom comparator.

The objects themselves also contain a list of pointers to other objects that identify some dependencies (the dependencies are not used in the comparator and do not affect the underlying tree structure of the set). Currently this dependency list is implemented as an std::list of raw object pointers. This is unsafe, however, because a dependency could be removed from the multiset without the objects holding these dependency pointers being notified.

Is there a way to use weak_ptr to point to the objects in the set without using shared_ptrs in the set itself? Or is the only way to accomplish this to have a set of share_ptrs instead of Objects?

sheridp
  • 1,386
  • 1
  • 11
  • 24
  • Could you move the "removed" objects to a separate container (instead of deleting them) and mark them as "deleted" until you can update the pointers in the tree? – Galik Jan 14 '19 at 22:20
  • That is interesting; it looks like I would have to use C++17 set::extract. The only problem I see is that I won't know (without iterating the whole tree) when there are no more references to the item in the separate container. This seems just as much work as scanning the whole tree and removing any pointers to the removed object. I think it would be probably be easier just to use shared_ptrs. – sheridp Jan 14 '19 at 22:43
  • Well I suppose you could also add a reference count to your objects and when you encounter a pointer to a "removed" object, decrement the reference count and remove the pointer. Its more involved but `shared_ptr` has a performance impact so it depends on if you need easier implementation or faster operation. – Galik Jan 14 '19 at 22:47
  • The only use of a `weak_ptr` is to promote it to a `shared_ptr` (if it's valid; else, you get a null or an exception), so while the `shared_ptr` interface can be implemented alone without support for a `weak_ptr`, the `weak_ptr` interface cannot be implemented alone. You can only do `weak_ptr` -> `shared_ptr` -> `T*`. And you can only keep using the raw pointer for as long as you keep the `shared_ptr`. Trying to cut the middle man and get a raw pointer from a weak smart pointer wouldn't produce a usable pointer. – curiousguy Jan 18 '19 at 11:24
  • @curiousguy, my thinking was more along the lines of `weak_ptr -> shared_ptr` (don't really have a need to get a raw pointer). What I didn't understand was that the reference counting machinery resides only in `shared_ptr`, so if you don't have a `shared_ptr` as the underlying element in the set, there's no getting a `weak_ptr` to it (as explained by Ben Voigt below). – sheridp Jan 18 '19 at 15:38
  • @sheridp "_the reference counting machinery resides only in shared_ptr_" No, that isn't what was explained. The refcount "machinery" is in the control block; it's created when a new original `shared_ptr` is created (original: not a copy of another `shared_ptr`). No refcounted smart *pointer* ever contains a refcount as a member, it wouldn't serve any purpose for an object with pointer semantics: the count is for the number of smart pointers that are copies; it isn't a property of a particular pointer but of the shared object. – curiousguy Jan 18 '19 at 19:10
  • @sheridp The only important event of a refcount is when it reaches zero: the last counter reference is dropping its ownership and the object becomes non-owned. That event happens at most once: the atomic counter is only once decremented to zero. With a design w/o weak ref, you have just one refcount, for the number of owners of the object. With a weak ref design, you need a second count, to detect when the last owner of the control block goes away: the weak count is >0 when there is either a strong ref or weak ref. – curiousguy Jan 18 '19 at 20:41
  • The control block is shared by weak and strong ref. For the weak ref, the role of the control block is to allow an atomic test and increment: `atomic{if (strong_count>0) { ++strong_count; return true; } else return false;}`where `atomic` is pseudo code for an operation made atomic WRT to all other accessed to the counter. **When there is zero `shared_ptr` owning the object, the strong_count is 0 and no `shared_ptr` can be made pointing to the object.** So it doesn't even make sense to ask for a `weak_ptr` without a `shared_ptr`, not just at the impl level, also the semantic level. – curiousguy Jan 18 '19 at 20:45

2 Answers2

2

std::weak_ptr actually points to the metadata block that std::shared_ptr uses to track both the object location and lifetime. If there's no shared_ptr, there's no metadata block.

It might be possible to design a weak pointer that doesn't rely on std::shared_ptr, but that would not be std::weak_ptr. The weak pointer and the container would have to cooperate at a very deep level -- you'd be replacing std::multiset, too.

I believe you may have a larger problem, however... if you are removing things from the std::multiset, that may invalidate pointers to all elements, not just the ones removed. Storing std::shared_ptr inside your set would solve both these problems at once.

Actually, struck out section doesn't apply to associative containers, including std::multiset. Other container types would not be safe. Multiset however guarantees that

the erase members shall invalidate only iterators and references to the erased elements

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
0

If you are going to use smart pointers to manage a pointer you should use smart pointers to manage all access to that pointer, otherwise you still have the same problem waiting for you.

Richard
  • 8,920
  • 2
  • 18
  • 24