Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a std::shared_ptr with the same control block as an expired std::weak_ptr

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:

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

  2. 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:

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

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

like image 368
leek Avatar asked Sep 01 '25 03:09

leek


1 Answers

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.

like image 86
Ben Voigt Avatar answered Sep 03 '25 09:09

Ben Voigt