Make Observer registration ownership and notification mutation memory-safe
Problem
Observer registration currently returns a raw owning IObserverHandle*. This creates several memory-safety risks:
- Losing the returned pointer permanently leaks the registration handle and lifetime-control reference.
- Registering the same Observer more than once returns the same raw pointer, allowing multiple callers to believe they own it and potentially double-delete it.
ThreadSafeObservableallocates a temporary Observer vector for every notification, creating unnecessary heap traffic and fragmentation risk on embedded systems.- Registering or unregistering Observers during notification can invalidate active containers, particularly with
ObservableWithBuckets. - Surviving handles are not immediately invalidated when Observable destruction begins.
Proposed changes
- Change
RegisterObserver()andRegisterObserverAs()to return:ObserverHandlePtr- Defined as
std::unique_ptr<IObserverHandle>
- Reject duplicate registrations with
DuplicateObserverRegistrationException. - Ensure every registration has exactly one owning handle.
- Automatically unregister when the owning handle is reset or destroyed.
- Invalidate all surviving handles during Observable destruction.
- Replace per-notification Observer-vector copies with indexed, allocation-free traversal.
- Use deferred tombstone compaction so Observers can safely unregister themselves or other Observers during notification.
- Apply equivalent mutation-safe behavior to:
ObservableThreadSafeObservableObservableWithBuckets
- Preserve exception safety when notification callbacks throw.
- Update documentation and examples for RAII handle ownership.
- Add regression coverage for:
- Duplicate registration rejection
- Self-unregistration during ordinary notification
- Self-unregistration during bucketed notification
- Thread-safe concurrent unregistration
- Handles outliving their Observable
- Observable destruction concurrent with handle destruction
- Exceptions during callbacks and unregistration
Version impact
This changes the public registration return type and ownership contract.
Target version: 3.0.0
Acceptance criteria
- Registration returns an owning smart pointer.
- Discarding or resetting a registration handle cannot leak it.
- Duplicate registration cannot create ambiguous ownership.
- Registration changes during notification cannot invalidate traversal.
- Thread-safe notification performs no temporary Observer-list allocation.
- Handles are invalidated when Observable destruction begins.
- Strict C++14 tests pass with warnings treated as errors.
- UBSan tests pass.
- Heap analysis reports no leaked allocations.