Skip to content

Commit 3d50b40

Browse files
committed
Added observable_sealed_ptr to fix release() dangling control block
1 parent db31147 commit 3d50b40

File tree

4 files changed

+975
-196
lines changed

4 files changed

+975
-196
lines changed

README.md

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# observable_unique_ptr<T, Deleter>
1+
# observable_unique_ptr<T, Deleter>, observable_sealed_ptr<T>, observer_ptr<T>
22

33
![Build Status](https://github.com/cschreib/observable_unique_ptr/actions/workflows/cmake.yml/badge.svg) ![Docs Build Status](https://github.com/cschreib/observable_unique_ptr/actions/workflows/doc.yml/badge.svg)
44

@@ -11,11 +11,13 @@ Built and tested on:
1111

1212
## Introduction
1313

14-
This is a small header-only library, providing a unique-ownership smart pointer `observable_unique_ptr` that can be observed with non-owning pointers `observer_ptr`. It is a mixture of `std::unique_ptr` and `std::shared_ptr`: it borrows the unique-ownership semantic of `std::unique_ptr` (movable, non-copiable), but allows creating `observer_ptr` to monitor the lifetime of the pointed object (like `std::weak_ptr` for `std::shared_ptr`).
14+
This is a small header-only library, providing the unique-ownership smart pointers `observable_unique_ptr` and `observable_sealed_ptr` that can be observed with non-owning pointers `observer_ptr`. This is a mixture of `std::unique_ptr` and `std::shared_ptr`: it borrows the unique-ownership semantic of `std::unique_ptr` (movable, non-copiable), but allows creating `observer_ptr` to monitor the lifetime of the pointed object (like `std::weak_ptr` for `std::shared_ptr`).
1515

16-
This is useful for cases where the shared-ownership of `std::shared_ptr` is not desirable, e.g., when lifetime must be carefully controlled and not be allowed to extend, yet non-owning/weak/observer references to the object may exist after the object has been deleted.
16+
The only difference between `observable_unique_ptr` and `observable_sealed_ptr` is that the former can release ownership, while the latter cannot. Disallowing release of ownership enables allocation optimizations. Therefore, the recommendation is to use `observable_sealed_ptr` unless release of ownership is required.
1717

18-
Note: Because of the unique ownership model, observer pointers cannot extend the lifetime of the pointed object, hence `observable_unique_ptr`/`observer_ptr` provides less thread-safety compared to `std::shared_ptr`/`std::weak_ptr`. This is also true of `std::unique_ptr`, and is a fundamental limitation of unique ownership. If this is an issue, you will need either to add your own explicit locking logic, or use `std::shared_ptr`/`std::weak_ptr`.
18+
These pointers are useful for cases where the shared-ownership of `std::shared_ptr` is not desirable, e.g., when lifetime must be carefully controlled and not be allowed to extend, yet non-owning/weak/observer references to the object may exist after the object has been deleted.
19+
20+
Note: Because of the unique ownership model, observer pointers cannot extend the lifetime of the pointed object, hence this library provides less thread-safety compared to `std::shared_ptr`/`std::weak_ptr`. This is also true of `std::unique_ptr`, and is a fundamental limitation of unique ownership. If this is an issue, you will need either to add your own explicit locking logic, or use `std::shared_ptr`/`std::weak_ptr`.
1921

2022

2123
## Usage
@@ -39,7 +41,7 @@ int main() {
3941

4042
{
4143
// Unique pointer that owns the object
42-
auto owner_ptr = oup::make_observable_unique<std::string>("hello");
44+
auto owner_ptr = oup::make_observable_sealed<std::string>("hello");
4345

4446
// Make the observer pointer point to the object
4547
obs_ptr = owner_ptr;
@@ -70,10 +72,10 @@ int main() {
7072

7173
## Limitations
7274

73-
The follownig limitations are features that were not implemented simply because of lack of motivation.
75+
The following limitations are features that were not implemented simply because of lack of motivation.
7476

75-
- `observable_unique_ptr` does not support pointers to arrays, but `std::unique_ptr` and `std::shared_ptr` both do.
76-
- `observable_unique_ptr` does not support custom allocators, but `std::shared_ptr` does.
77+
- this library does not support pointers to arrays, but `std::unique_ptr` and `std::shared_ptr` both do.
78+
- this library does not support custom allocators, but `std::shared_ptr` does.
7779

7880

7981
## Comparison spreadsheet
@@ -86,31 +88,35 @@ Labels:
8688
- weak: `std::weak_ptr<T>`
8789
- shared: `std::shared_ptr<T>`
8890
- observer: `oup::observable_ptr<T>`
89-
- observable_unique: `oup::observable_unique_ptr<T>`
90-
91-
| Pointer | raw | weak | observer | unique | shared | observable_unique |
92-
|--------------------------|------|--------|----------|--------|--------|-------------------|
93-
| Owning | no | no | no | yes | yes | yes |
94-
| Observable deletion | no | yes | yes | yes | yes | yes |
95-
| Thread safe deletion | no | yes | no(1) | yes(2) | yes | yes(2) |
96-
| Atomic | yes | no(3) | no | no | no(3) | no |
97-
| Support arrays | yes | yes | no | yes | yes | no |
98-
| Support custom allocator | yes | yes | no | yes | yes | no |
99-
| Size in bytes (64 bit) | | | | | | |
100-
| - Stack (per instance) | 8 | 16 | 16 | 8 | 16 | 16 |
101-
| - Heap (shared) | 0 | 0 | 0 | 0 | 24 | 8 |
102-
| - Total | 8 | 16 | 16 | 8 | 40 | 24 |
103-
| Size in bytes (32 bit) | | | | | | |
104-
| - Stack (per instance) | 4 | 8 | 8 | 4 | 8 | 8 |
105-
| - Heap (shared) | 0 | 0 | 0 | 0 | 16 | 8 |
106-
| - Total | 4 | 8 | 8 | 4 | 24 | 16 |
91+
- obs_unique: `oup::observable_unique_ptr<T>`
92+
- obs_sealed: `oup::observable_sealed_ptr<T>`
93+
94+
| Pointer | raw | weak | observer | unique | shared | obs_unique | obs_sealed |
95+
|--------------------------|------|--------|----------|--------|--------|------------|------------|
96+
| Owning | no | no | no | yes | yes | yes | yes |
97+
| Releasable | N/A | N/A | N/A | yes | no | yes | no |
98+
| Observable deletion | no | yes | yes | yes | yes | yes | yes |
99+
| Thread safe deletion | no | yes | no(1) | yes(2) | yes | yes(2) | yes(2) |
100+
| Atomic | yes | no(3) | no | no | no(3) | no | no |
101+
| Support arrays | yes | yes | no | yes | yes | no | no |
102+
| Support custom allocator | yes | yes | no | yes | yes | no | no |
103+
| Number of heap alloc. | 0 | 0 | 0 | 1 | 1 or 2 | 2 | 1 |
104+
| Size in bytes (64 bit) | | | | | | | |
105+
| - Stack (per instance) | 8 | 16 | 16 | 8 | 16 | 16 | 16 |
106+
| - Heap (shared) | 0 | 0 | 0 | 0 | 24 | 8 | 8 |
107+
| - Total | 8 | 16 | 16 | 8 | 40 | 24 | 24 |
108+
| Size in bytes (32 bit) | | | | | | | |
109+
| - Stack (per instance) | 4 | 8 | 8 | 4 | 8 | 8 | 8 |
110+
| - Heap (shared) | 0 | 0 | 0 | 0 | 16 | 8 | 8 |
111+
| - Total | 4 | 8 | 8 | 4 | 24 | 16 | 16 |
107112

108113
Notes:
109114

110-
- (1) If `expired()` returns true, the pointer is garanteed to remain `nullptr` forever, with no ace condition. If `expired()` returns false, the pointer could still expire on the next instant, which can lead to race conditions.
115+
- (1) If `expired()` returns true, the pointer is guaranteed to remain `nullptr` forever, with no ace condition. If `expired()` returns false, the pointer could still expire on the next instant, which can lead to race conditions.
111116
- (2) By construction, only one thread can own the pointer, therefore deletion is thread-safe.
112117
- (3) Yes if using `std::atomic<std::shared_ptr<T>>` and `std::atomic<std::weak_ptr<T>>`.
113118

119+
114120
## Notes
115121

116122
### Alternative implementation

0 commit comments

Comments
 (0)