Skip to content

Introduce policies and allow observer_from_this in constructors #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
88c840a
Added compile-time sealed ptr checks
cschreib Jan 2, 2022
a1e875c
Use policies to customize smart pointers
cschreib Jan 2, 2022
bdd9d1d
Fixed reset control block bypassed
cschreib Jan 2, 2022
44666e4
Simplify check for has_enable_observer_from_this
cschreib Jan 2, 2022
d77fa70
Allow observer_from_this in constructor for Policy::allow_release
cschreib Jan 2, 2022
888f9b7
Updated documentation
cschreib Jan 3, 2022
abe2862
Do not expire observers on release() if enable_from_this
cschreib Jan 3, 2022
556250f
Make reset(nullptr) behave exactly like reset((T*)nullptr)
cschreib Jan 3, 2022
65c20c6
Use uniform typedef for control_block_type
cschreib Jan 3, 2022
35d533f
Rename allow_release to is_sealed
cschreib Jan 3, 2022
606a771
Added observer_from_this for sealed pointer
cschreib Jan 3, 2022
3cd8898
Removed shortcut to has_enable_observer_from_this
cschreib Jan 3, 2022
2ab7695
Fixed enable_observer_from_this_base access control
cschreib Jan 3, 2022
4c28e07
Finished documentation
cschreib Jan 3, 2022
114a177
Added missing friend for virtual sealed
cschreib Jan 3, 2022
148e455
Added tests for virtual sealed
cschreib Jan 3, 2022
bf7b956
Added test for bad_observer_from_this
cschreib Jan 3, 2022
2a02d04
Simplify set_control_block_()
cschreib Jan 3, 2022
477ee03
Added tests for other non-standard policies
cschreib Jan 3, 2022
3cf2db6
Added missing test for non virtual unique thrower
cschreib Jan 3, 2022
e34295a
Updated readme
cschreib Jan 3, 2022
2831085
Made control block type public, for easier use
cschreib Jan 4, 2022
cfae72c
Clean up code for control block
cschreib Jan 5, 2022
e0a1cc5
Added delete_if_exists_() for clarity
cschreib Jan 6, 2022
633f80d
Added final keyword
cschreib Jan 6, 2022
783f969
Cleanup policies and added policy_query
cschreib Jan 6, 2022
a08d5cd
Updated documentation
cschreib Jan 6, 2022
bc8042b
Updated readme
cschreib Jan 6, 2022
f5f6dac
Using policy to set the max number of observers
cschreib Jan 7, 2022
b2961da
Updated readme
cschreib Jan 7, 2022
3298e5d
Fix for cmath not being constexpr
cschreib Jan 7, 2022
f2111e6
Added more information on eoft policies
cschreib Jan 7, 2022
4cfd465
Clean up documentation
cschreib Jan 7, 2022
4cb1674
Cleanup floor_log2
cschreib Jan 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Built and tested on:

- [Introduction](#introduction)
- [Usage](#usage)
- [enable_observer_from_this](#enable_observer_from_this)
- [Policies](#policies)
- [Limitations](#limitations)
- [Comparison spreadsheet](#comparison-spreadsheet)
- [Speed benchmarks](#speed-benchmarks)
Expand Down Expand Up @@ -83,7 +85,19 @@ int main() {
}
```

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.

## enable_observer_from_this

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.

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.


## Policies

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>`).

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).


## Limitations
Expand Down Expand Up @@ -117,23 +131,25 @@ Labels:
| Support arrays | yes | yes | no | yes | yes | no | no |
| Support custom allocator | N/A | yes | no | yes | yes | no | no |
| Support custom deleter | N/A | N/A | N/A | yes | yes(4) | yes | no |
| Number of heap alloc. | 0 | 0 | 0 | 1 | 1/2(5) | 2 | 1 |
| Max number of observers | inf. | ?(5) | 2^31 - 1 | 1 | ?(5) | 1 | 1 |
| Number of heap alloc. | 0 | 0 | 0 | 1 | 1/2(6) | 2 | 1 |
| Size in bytes (64 bit) | | | | | | | |
| - Stack (per instance) | 8 | 16 | 16 | 8 | 16 | 16 | 16 |
| - Heap (shared) | 0 | 0 | 0 | 0 | 24 | 8 | 8 |
| - Total | 8 | 16 | 16 | 8 | 40 | 24 | 24 |
| - Heap (shared) | 0 | 0 | 0 | 0 | 24 | 4 | 4 |
| - Total | 8 | 16 | 16 | 8 | 40 | 20 | 20 |
| Size in bytes (32 bit) | | | | | | | |
| - Stack (per instance) | 4 | 8 | 8 | 4 | 8 | 8 | 8 |
| - Heap (shared) | 0 | 0 | 0 | 0 | 16 | 8 | 8 |
| - Total | 4 | 8 | 8 | 4 | 24 | 16 | 16 |
| - Heap (shared) | 0 | 0 | 0 | 0 | 16 | 4 | 4 |
| - Total | 4 | 8 | 8 | 4 | 24 | 12 | 12 |

Notes:

- (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.
- (2) By construction, only one thread can own the pointer, therefore deletion is thread-safe.
- (3) Yes if using `std::atomic<std::shared_ptr<T>>` and `std::atomic<std::weak_ptr<T>>`.
- (4) Not if using `std::make_shared()`.
- (5) 2 by default, or 1 if using `std::make_shared()`.
- (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.
- (6) 2 by default, or 1 if using `std::make_shared()`.


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

The benchmarks were last ran for v0.4.0.

*Compiler: gcc 9.3.0, std: libstdc++, OS: linux 5.1.0, CPU: Ryzen 5 2600:*

| Pointer | raw/unique | weak/shared | observer/obs_unique | observer/obs_sealed |
Expand Down
8 changes: 4 additions & 4 deletions doc/dox.conf
Original file line number Diff line number Diff line change
Expand Up @@ -514,22 +514,22 @@ EXTRACT_ANON_NSPACES = NO
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.

HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_MEMBERS = YES

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

HIDE_UNDOC_CLASSES = NO
HIDE_UNDOC_CLASSES = YES

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

HIDE_FRIEND_COMPOUNDS = NO
HIDE_FRIEND_COMPOUNDS = YES

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

EXCLUDE_SYMBOLS =
EXCLUDE_SYMBOLS = T, Deleter, enable_observer_from_this_base, inherit_as_virtual, ptr_and_deleter, details

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