Skip to content

Commit 1053747

Browse files
authored
Merge pull request #6 from cschreib/policy
Introduce policies and allow `observer_from_this` in constructors
2 parents d3ac46d + 4cb1674 commit 1053747

8 files changed

+1501
-974
lines changed

README.md

+25-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Built and tested on:
1313

1414
- [Introduction](#introduction)
1515
- [Usage](#usage)
16+
- [enable_observer_from_this](#enable_observer_from_this)
17+
- [Policies](#policies)
1618
- [Limitations](#limitations)
1719
- [Comparison spreadsheet](#comparison-spreadsheet)
1820
- [Speed benchmarks](#speed-benchmarks)
@@ -83,7 +85,19 @@ int main() {
8385
}
8486
```
8587

86-
As with `std::shared_ptr`/`std::weak_ptr`, if you need to obtain an observer pointer to an object when you only have `this` (i.e., from a member function), you can inherit from `oup::enable_observer_from_this<T>` to gain access to the `observer_from_this()` member function. This function will return a valid observer pointer as long as the object is owned by a unique or sealed pointer, and will return `nullptr` in all other cases. Contrary to `std::enable_shared_from_this<T>`, this feature naturally supports multiple inheritance.
88+
89+
## enable_observer_from_this
90+
91+
As with `std::shared_ptr`/`std::weak_ptr`, if you need to obtain an observer pointer to an object when you only have `this` (i.e., from a member function), you can inherit from `oup::enable_observer_from_this_unique<T>` or `oup::enable_observer_from_this_sealed<T>` (depending on the type of the owner pointer) to gain access to the `observer_from_this()` member function. Contrary to `std::enable_shared_from_this<T>`, this function is `noexcept` and is able to return a valid observer pointer at all times, even if the object is being constructed or is not owned by a unique or sealed pointer. Also contrary to `std::enable_shared_from_this<T>`, this feature naturally supports multiple inheritance.
92+
93+
To achieve this, the price to pay is that `oup::enable_observer_from_this_unique<T>` uses virtual inheritance, while `oup::enable_observer_from_this_sealed<T>` requires `T`'s constructor to take a control block as input (thereby preventing `T` from being default-constructible, copiable, or movable). If needed, these trade-offs can be controlled by policies, see below.
94+
95+
96+
## Policies
97+
98+
Similarly to `std::string` and `std::basic_string`, this library provides both "convenience" types (`oup::observable_unique_ptr<T,Deleter>`, `oup::observable_sealed_ptr<T>`, `oup::observer_ptr<T>`, `oup::enable_observable_from_this_unique<T>`, `oup::enable_observable_from_this_sealed<T>`) and "generic" types (`oup::basic_observable_ptr<T,Deleter,Policy>`, `oup::basic_observer_ptr<T,ObsPolicy>`, `oup::basic_enable_observable_from_this<T,Policy>`).
99+
100+
If the trade-offs chosen to defined the "convenience" types are not appropriate for your use cases, they can be fine-tuned using the generic classes and providing your own choice of policies. Please refer to the documentation for more information on policies. In particular, policies will control most of the API and behavior of the `enable_observable_from_this` feature, as well as allowing you to tune the size of the reference counting object (speed/memory trade-off).
87101

88102

89103
## Limitations
@@ -117,23 +131,25 @@ Labels:
117131
| Support arrays | yes | yes | no | yes | yes | no | no |
118132
| Support custom allocator | N/A | yes | no | yes | yes | no | no |
119133
| Support custom deleter | N/A | N/A | N/A | yes | yes(4) | yes | no |
120-
| Number of heap alloc. | 0 | 0 | 0 | 1 | 1/2(5) | 2 | 1 |
134+
| Max number of observers | inf. | ?(5) | 2^31 - 1 | 1 | ?(5) | 1 | 1 |
135+
| Number of heap alloc. | 0 | 0 | 0 | 1 | 1/2(6) | 2 | 1 |
121136
| Size in bytes (64 bit) | | | | | | | |
122137
| - Stack (per instance) | 8 | 16 | 16 | 8 | 16 | 16 | 16 |
123-
| - Heap (shared) | 0 | 0 | 0 | 0 | 24 | 8 | 8 |
124-
| - Total | 8 | 16 | 16 | 8 | 40 | 24 | 24 |
138+
| - Heap (shared) | 0 | 0 | 0 | 0 | 24 | 4 | 4 |
139+
| - Total | 8 | 16 | 16 | 8 | 40 | 20 | 20 |
125140
| Size in bytes (32 bit) | | | | | | | |
126141
| - Stack (per instance) | 4 | 8 | 8 | 4 | 8 | 8 | 8 |
127-
| - Heap (shared) | 0 | 0 | 0 | 0 | 16 | 8 | 8 |
128-
| - Total | 4 | 8 | 8 | 4 | 24 | 16 | 16 |
142+
| - Heap (shared) | 0 | 0 | 0 | 0 | 16 | 4 | 4 |
143+
| - Total | 4 | 8 | 8 | 4 | 24 | 12 | 12 |
129144

130145
Notes:
131146

132147
- (1) If `expired()` returns true, the pointer is guaranteed to remain `nullptr` forever, with no race condition. If `expired()` returns false, the pointer could still expire on the next instant, which can lead to race conditions.
133148
- (2) By construction, only one thread can own the pointer, therefore deletion is thread-safe.
134149
- (3) Yes if using `std::atomic<std::shared_ptr<T>>` and `std::atomic<std::weak_ptr<T>>`.
135150
- (4) Not if using `std::make_shared()`.
136-
- (5) 2 by default, or 1 if using `std::make_shared()`.
151+
- (5) Not defined by the C++ standard. In practice, libstdc++ stores its reference count on an `_Atomic_word`, which for a common 64bit linux platform is a 4 byte signed integer, hence the limit will be 2^31 - 1. Microsoft's STL uses `_Atomic_counter_t`, which for a 64bit Windows platform is 4 bytes unsigned integer, hence the limit will be 2^32 - 1.
152+
- (6) 2 by default, or 1 if using `std::make_shared()`.
137153

138154

139155
## Speed benchmarks
@@ -152,6 +168,8 @@ Detail of the benchmarks:
152168
- Create observer copy: construct a new observer pointer from another observer pointer.
153169
- Dereference observer: get a reference to the underlying object from an observer pointer.
154170

171+
The benchmarks were last ran for v0.4.0.
172+
155173
*Compiler: gcc 9.3.0, std: libstdc++, OS: linux 5.1.0, CPU: Ryzen 5 2600:*
156174

157175
| Pointer | raw/unique | weak/shared | observer/obs_unique | observer/obs_sealed |

doc/dox.conf

+4-4
Original file line numberDiff line numberDiff line change
@@ -514,22 +514,22 @@ EXTRACT_ANON_NSPACES = NO
514514
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
515515
# The default value is: NO.
516516

517-
HIDE_UNDOC_MEMBERS = NO
517+
HIDE_UNDOC_MEMBERS = YES
518518

519519
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
520520
# undocumented classes that are normally visible in the class hierarchy. If set
521521
# to NO, these classes will be included in the various overviews. This option
522522
# has no effect if EXTRACT_ALL is enabled.
523523
# The default value is: NO.
524524

525-
HIDE_UNDOC_CLASSES = NO
525+
HIDE_UNDOC_CLASSES = YES
526526

527527
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
528528
# declarations. If set to NO, these declarations will be included in the
529529
# documentation.
530530
# The default value is: NO.
531531

532-
HIDE_FRIEND_COMPOUNDS = NO
532+
HIDE_FRIEND_COMPOUNDS = YES
533533

534534
# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
535535
# documentation blocks found inside the body of a function. If set to NO, these
@@ -889,7 +889,7 @@ EXCLUDE_PATTERNS =
889889
# Note that the wildcards are matched against the file with absolute path, so to
890890
# exclude all test directories use the pattern */test/*
891891

892-
EXCLUDE_SYMBOLS =
892+
EXCLUDE_SYMBOLS = T, Deleter, enable_observer_from_this_base, inherit_as_virtual, ptr_and_deleter, details
893893

894894
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
895895
# that contain example code fragments that are included (see the \include

0 commit comments

Comments
 (0)