diff --git a/ctl/shared_ptr.h b/ctl/shared_ptr.h index 8bb89294311..df386537774 100644 --- a/ctl/shared_ptr.h +++ b/ctl/shared_ptr.h @@ -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" @@ -437,6 +438,9 @@ class weak_ptr template friend class shared_ptr; + template + friend shared_ptr make_shared(Args&&...); + element_type* p = nullptr; __::shared_ref* rc = nullptr; }; @@ -497,19 +501,75 @@ shared_ptr::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 l, r; +// ctl::weak_ptr parent; +// Tree(weak_ptr const& self, auto&& l2, auto&& r2) +// : l(ctl::forward(l2)), +// r(ctl::forward(r2)) +// { +// if (l) l->parent = self; +// if (r) r->parent = self; +// } +// }; +// +// int main() { +// auto t = ctl::make_shared( +// ctl::make_shared(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 shared_ptr make_shared(Args&&... args) { - auto rc = __::shared_emplace::make(); - rc->construct(forward(args)...); - shared_ptr r; - r.p = &rc->t; - r.rc = rc.release(); - if constexpr (is_base_of_v, T>) { - r->weak_this = r; - } - return r; + unique_ptr rc = __::shared_emplace::make(); + if constexpr (is_base_of_v && + is_constructible_v&, 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 w; + w.p = &rc->t; + w.rc = rc.get(); + try { + rc->construct(const_cast&>(w), + forward(args)...); + } catch (...) { + w.p = nullptr; + w.rc = nullptr; + throw; + } + rc.release(); + shared_ptr r; + swap(r.p, w.p); + swap(r.rc, w.rc); + return r; + } else { + rc->construct(forward(args)...); + shared_ptr r; + r.p = &rc->t; + r.rc = rc.release(); + if constexpr (is_base_of_v, T>) { + r->weak_this = r; + } + return r; + } } } // namespace ctl diff --git a/test/ctl/shared_ptr_test.cc b/test/ctl/shared_ptr_test.cc index c910ddf7029..f9a8dd59737 100644 --- a/test/ctl/shared_ptr_test.cc +++ b/test/ctl/shared_ptr_test.cc @@ -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" @@ -88,6 +89,21 @@ class SharedThis : public enable_shared_from_this class CanShareThis : public enable_shared_from_this {}; +// Sample class used to demonstrate the CTL shared_ptr's weak_self feature. +struct Tree : ctl::weak_self_base +{ + ctl::shared_ptr l, r; + ctl::weak_ptr p; + Tree(ctl::weak_ptr const& self, auto&& l2, auto&& r2) + : l(ctl::forward(l2)), r(ctl::forward(r2)) + { + if (l) + l->p = self; + if (r) + r->p = self; + } +}; + int main() { @@ -276,6 +292,19 @@ main() return 25; } + if constexpr (ctl::is_same_v, ctl::shared_ptr>) { + // Exercise our off-STL make_shared with weak self support. + auto t = ctl::make_shared( + ctl::make_shared(ctl::make_shared(nullptr, nullptr), + nullptr), + ctl::make_shared(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.