TL;DR How can a std::shared_ptr be created from an expired std::weak_ptr so that it uses the same control block as the std::weak_ptr?
I ran the following test case, and noticed two oddities:
#include <memory>
#include <cstdio>
#define TEST(a, b) printf("%14s has %9s control block as %s\n", #a, \
a.owner_before(b) || b.owner_before(a) ? "different" : "same", #b)
int main() {
std::weak_ptr<int> empty { };
std::weak_ptr<int> expired { std::make_shared<int>() };
std::shared_ptr<int> locked_expired{ expired.lock() };
std::shared_ptr<int> null_nonempty { static_cast<int*>(nullptr) };
std::shared_ptr<int> null_empty { nullptr };
TEST(locked_expired, expired);
TEST(locked_expired, empty );
TEST(null_nonempty, empty );
TEST(null_empty, empty );
}
Outputs:
locked_expired has different control block as expired
locked_expired has same control block as empty
null_nonempty has different control block as empty
null_empty has same control block as empty
There are two oddities:
expired.lock() creates a std::shared_ptr which has a different control block than expired, and which has the same (no) control block as an empty std::weak_ptr / std::shared_ptr.
null_empty constructed from nullptr has the same (no) control block as an empty default-constructed std::shared_ptr / std::weak_ptr, while null_nonempty constructed from static_cast<int*>(nullptr) has a different control block than an empty std::weak_ptr.
This is different from what I would have expected:
I would expect that it should create a std::shared_ptr with the same control block as expired, but of course with a null pointer stored in it (.get() == nullptr) since it was expired.
I would expect that a std::shared_ptr constructed from a nullptr to behave the same as a std::shared_ptr constructed from static_cast<element_type*>(nullptr) and create a std::shared_ptr with a new control block, different from a default-constructed std::shared_ptr with no control block.
I am not concerned with #2, since it is easily worked around. But #1 seems intractable.
How can a std::shared_ptr be created from an expired std::weak_ptr so that it shares the same control block as the std::weak_ptr, ie expired.lock() => std::shared_ptr using same control block and ownership as expired but with a null pointer stored in it?
I am interested in preserving ownership relationships between arbitrary std::shared_ptr and std::weak_ptr instances, and am more concerned about the control blocks than about the managed storage.
The owner_less relation is specified so that any two empty smart pointers compare equivalent. This constrains the behavior that the result of lock() on an expired weak_ptr must be equivalent to all other default-constructed shared_ptrs.
Additionally, the design of shared_ptr excludes the possibility of being attached to an expired control block. Changing this would eliminate an important invariant and force expiration checks on all shared_ptr operations which currently are not needed, slowing down all users.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With