C++ Logo

std-proposals

Advanced search

Re: [std-proposals] A non-owning but self zeroing smart pointer for single ownership

From: JOHN MORRISON <inglesflamenco_at_[hidden]>
Date: Sun, 16 Feb 2025 12:12:17 +0000
  *
https://github.com/make-cpp-nice/ptr_to_unique A smart pointer to an object already owned by a unique_ptr. It doesn't own the object but it self zeroes when the object is deleted so that it can never dangle.

The main objection I am seeing is the lack of thread safety with the proposed ptr_to_unique. To be clear, it is perfectly safe to use in multiple threads but it is not safe to use it to reference a unique_ptr which is exposed to the actions of another thread. This is outside of its scope because it has no means of sharing ownership to keep the pointee alive after testing.

The kind of bug that could occur when something tests valid and then dies as you are using it is particular nasty because it requires an unfortunate coincidence to occur so it will be intermittent and hard to repeat. So we definitely don't want to go there.

However there is no reason why we should. We already have well established and well trodden disciplines to manage multithreading and they are sufficient to prevent such a thing from occurring. The compiler is unable to enforce these rules but we do because we know that breaching them risks undefined behaviour. They can be simply stated as:


  1.
If data is visible to multiple threads then it must have shared ownership.
  2.
If data has single ownership then it must not be visible to any other thread.

In practice we follow these rules indirectly by how we manage two distinct approaches to multithreading and ownership.

The first has multiple running threads sharing access to the same data. Its discipline is that such objects should be held by shared_ptr, have access synchronisation if they are writeable and any further persistent references to it should be shared_ptr or weak_ptr. The proposed ptr_to_unique has no role here because everything is shared ownership – you would first have to break the rule of holding the data with a shared_ptr.

The second is strongly focused on using single ownership for its assured and immediate deterministic destruction and unique_ptr is used for this. Thread safety is achieved by never allowing running threads simultaneous access to the same data. You can pass ownership to another thread so it has it and you don't but no two threads should have simultaneous access to it. With this discipline you never hold a reference to something being worked on by another thread so your ptr_to_uniques are safe to use.

The proposed ptr_to_unique has no place in the discipline of the first approach and it sits well in the discipline of the second approach for which it is designed.

Code can be a mixture of the two approaches and that is no problem, Data that can be accessed by multiple threads will be held by shared_ptr and data that is never visible to multiple threads can be held by unique_ptr. The proposed ptr_to_unique will only be able reference the unique_ptr (non multithread visible) held data which it can do safely.

Before you can run into the undefined behaviour risk of the proposed ptr_to_unique, you have to take other rule breaking steps that will expose you to undefined behaviour anyway. So does this really matter? Is it a reason for rejection if you can only hit its vulnerability if you have already screwed up? The only thing that needs to be made clear is that the introduction of ptr_to_unique does not change the rules.

The purpose of ptr_to_unique is to provide the utility of safe testable secondary references for single ownership. As we already take care not to expose single ownership to other threads and will die if we don't, it is safe to use.

I think I need to say a bit more about why it is useful. My need for this arose in the development of event driven GUI applications which are a form of cooperative multitasking all running in one thread. A lot of lifetimes can come and go but all the event handlers that you write are running in the same thread. Typically I will be displaying a polymorphic collection of objects and may run a procedure to pick out some that require special attention. I don't want to run that procedure every time the screen is repainted so I hold pointers to those objects. If the pointer is still valid, I use it. If not then its do nothing or run the procedure again. It was a necessary optimisation to prevent screen lag and I used to write a lot of very intrusive and fragile spaghetti code to make sure that those pointers were zeroed if their pointee was deleted.

I considered using shared_ptr/weak_ptr to get access to a non-owning smart pointer but the switch to shared ownership would be artificial, not correctly represent the situation and be exposed to unintended memory retention. When the user deletes an object it should be gone and not just removed from the collection but still persisting as a phantom object still being referenced by something. It has to be single ownership.

The proposed ptr_to_unique solves this problem but also came with a bonus. Using ptr_to_unique I can now have members of my collection safely holding non-owning references to each other. That is cool and opens up new design opportunities. Without it there is no way I could have written the spaghetti code needed to manage that.


Received on 2025-02-16 12:12:22