Skip to content

Add weak self make_shared variant #1299

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 2 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 69 additions & 9 deletions ctl/shared_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "exception.h"
#include "is_base_of.h"
#include "is_constructible.h"
#include "is_convertible.h"
#include "remove_extent.h"
#include "unique_ptr.h"
Expand Down Expand Up @@ -437,6 +438,9 @@ class weak_ptr
template<typename U>
friend class shared_ptr;

template<typename U, typename... Args>
friend shared_ptr<U> make_shared(Args&&...);

element_type* p = nullptr;
__::shared_ref* rc = nullptr;
};
Expand Down Expand Up @@ -497,19 +501,75 @@ shared_ptr<T>::shared_ptr(U* const p, D d)
}
}

// Our make_shared supports passing a weak self reference as the first parameter
// to your constructor, e.g.:
//
// struct Tree : ctl::weak_self_base
// {
// ctl::shared_ptr<Tree> l, r;
// ctl::weak_ptr<Tree> parent;
// Tree(weak_ptr<Tree> const& self, auto&& l2, auto&& r2)
// : l(ctl::forward<decltype(l2)>(l2)),
// r(ctl::forward<decltype(r2)>(r2))
// {
// if (l) l->parent = self;
// if (r) r->parent = self;
// }
// };
//
// int main() {
// auto t = ctl::make_shared<Tree>(
// ctl::make_shared<Tree>(nullptr, nullptr), nullptr);
// return t->l->parent.lock().get() == t.get() ? 0 : 1;
// }
//
// As shown, passing the parameter at object construction time lets you complete
// object construction without needing a separate Init method. But because we go
// off spec as far as the STL is concerned, there is a potential ambiguity where
// you might have a constructor with a weak_ptr first parameter that is intended
// to be something other than a self-reference. So this feature is opt-in by way
// of inheriting from the following struct.
struct weak_self_base
{};

template<typename T, typename... Args>
shared_ptr<T>
make_shared(Args&&... args)
{
auto rc = __::shared_emplace<T>::make();
rc->construct(forward<Args>(args)...);
shared_ptr<T> r;
r.p = &rc->t;
r.rc = rc.release();
if constexpr (is_base_of_v<enable_shared_from_this<T>, T>) {
r->weak_this = r;
}
return r;
unique_ptr rc = __::shared_emplace<T>::make();
if constexpr (is_base_of_v<weak_self_base, T> &&
is_constructible_v<T, const weak_ptr<T>&, Args...>) {
// A __::shared_ref has a virtual weak reference that is owned by all of
// the shared references. We can avoid some unnecessary refcount changes
// by "borrowing" that reference and passing it to the constructor, then
// promoting it to a shared reference by swapping it with the shared_ptr
// that we return.
weak_ptr<T> w;
w.p = &rc->t;
w.rc = rc.get();
try {
rc->construct(const_cast<const weak_ptr<T>&>(w),
forward<Args>(args)...);
} catch (...) {
w.p = nullptr;
w.rc = nullptr;
throw;
}
rc.release();
shared_ptr<T> r;
swap(r.p, w.p);
swap(r.rc, w.rc);
return r;
} else {
rc->construct(forward<Args>(args)...);
shared_ptr<T> r;
r.p = &rc->t;
r.rc = rc.release();
if constexpr (is_base_of_v<enable_shared_from_this<T>, T>) {
r->weak_this = r;
}
return r;
}
}

} // namespace ctl
Expand Down
29 changes: 29 additions & 0 deletions test/ctl/shared_ptr_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include "ctl/is_same.h"
#include "ctl/shared_ptr.h"
#include "ctl/vector.h"
#include "libc/mem/leaks.h"
Expand Down Expand Up @@ -88,6 +89,21 @@ class SharedThis : public enable_shared_from_this<SharedThis>
class CanShareThis : public enable_shared_from_this<CanShareThis>
{};

// Sample class used to demonstrate the CTL shared_ptr's weak_self feature.
struct Tree : ctl::weak_self_base
{
ctl::shared_ptr<Tree> l, r;
ctl::weak_ptr<Tree> p;
Tree(ctl::weak_ptr<Tree> const& self, auto&& l2, auto&& r2)
: l(ctl::forward<decltype(l2)>(l2)), r(ctl::forward<decltype(r2)>(r2))
{
if (l)
l->p = self;
if (r)
r->p = self;
}
};

int
main()
{
Expand Down Expand Up @@ -276,6 +292,19 @@ main()
return 25;
}

if constexpr (ctl::is_same_v<shared_ptr<Tree>, ctl::shared_ptr<Tree>>) {
// Exercise our off-STL make_shared with weak self support.
auto t = ctl::make_shared<Tree>(
ctl::make_shared<Tree>(ctl::make_shared<Tree>(nullptr, nullptr),
nullptr),
ctl::make_shared<Tree>(nullptr, nullptr));
auto t2 = t->l->l->p.lock()->p.lock();
if (t.owner_before(t2) || t2.owner_before(t))
return 26;
if (!t.owner_before(t->l) && !t->l.owner_before(t))
return 27;
}

// TODO(mrdomino): exercise threads / races. The reference count should be
// atomically maintained.

Expand Down
Loading