Skip to content

Commit 1bfb348

Browse files
authored
Add weak self make_shared variant (#1299)
This extends the CTL version of make_shared with functionality not found in the STL, with inspiration taken from Rust's Rc class.
1 parent aaed879 commit 1bfb348

File tree

2 files changed

+98
-9
lines changed

2 files changed

+98
-9
lines changed

ctl/shared_ptr.h

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "exception.h"
77
#include "is_base_of.h"
8+
#include "is_constructible.h"
89
#include "is_convertible.h"
910
#include "remove_extent.h"
1011
#include "unique_ptr.h"
@@ -437,6 +438,9 @@ class weak_ptr
437438
template<typename U>
438439
friend class shared_ptr;
439440

441+
template<typename U, typename... Args>
442+
friend shared_ptr<U> make_shared(Args&&...);
443+
440444
element_type* p = nullptr;
441445
__::shared_ref* rc = nullptr;
442446
};
@@ -497,19 +501,75 @@ shared_ptr<T>::shared_ptr(U* const p, D d)
497501
}
498502
}
499503

504+
// Our make_shared supports passing a weak self reference as the first parameter
505+
// to your constructor, e.g.:
506+
//
507+
// struct Tree : ctl::weak_self_base
508+
// {
509+
// ctl::shared_ptr<Tree> l, r;
510+
// ctl::weak_ptr<Tree> parent;
511+
// Tree(weak_ptr<Tree> const& self, auto&& l2, auto&& r2)
512+
// : l(ctl::forward<decltype(l2)>(l2)),
513+
// r(ctl::forward<decltype(r2)>(r2))
514+
// {
515+
// if (l) l->parent = self;
516+
// if (r) r->parent = self;
517+
// }
518+
// };
519+
//
520+
// int main() {
521+
// auto t = ctl::make_shared<Tree>(
522+
// ctl::make_shared<Tree>(nullptr, nullptr), nullptr);
523+
// return t->l->parent.lock().get() == t.get() ? 0 : 1;
524+
// }
525+
//
526+
// As shown, passing the parameter at object construction time lets you complete
527+
// object construction without needing a separate Init method. But because we go
528+
// off spec as far as the STL is concerned, there is a potential ambiguity where
529+
// you might have a constructor with a weak_ptr first parameter that is intended
530+
// to be something other than a self-reference. So this feature is opt-in by way
531+
// of inheriting from the following struct.
532+
struct weak_self_base
533+
{};
534+
500535
template<typename T, typename... Args>
501536
shared_ptr<T>
502537
make_shared(Args&&... args)
503538
{
504-
auto rc = __::shared_emplace<T>::make();
505-
rc->construct(forward<Args>(args)...);
506-
shared_ptr<T> r;
507-
r.p = &rc->t;
508-
r.rc = rc.release();
509-
if constexpr (is_base_of_v<enable_shared_from_this<T>, T>) {
510-
r->weak_this = r;
511-
}
512-
return r;
539+
unique_ptr rc = __::shared_emplace<T>::make();
540+
if constexpr (is_base_of_v<weak_self_base, T> &&
541+
is_constructible_v<T, const weak_ptr<T>&, Args...>) {
542+
// A __::shared_ref has a virtual weak reference that is owned by all of
543+
// the shared references. We can avoid some unnecessary refcount changes
544+
// by "borrowing" that reference and passing it to the constructor, then
545+
// promoting it to a shared reference by swapping it with the shared_ptr
546+
// that we return.
547+
weak_ptr<T> w;
548+
w.p = &rc->t;
549+
w.rc = rc.get();
550+
try {
551+
rc->construct(const_cast<const weak_ptr<T>&>(w),
552+
forward<Args>(args)...);
553+
} catch (...) {
554+
w.p = nullptr;
555+
w.rc = nullptr;
556+
throw;
557+
}
558+
rc.release();
559+
shared_ptr<T> r;
560+
swap(r.p, w.p);
561+
swap(r.rc, w.rc);
562+
return r;
563+
} else {
564+
rc->construct(forward<Args>(args)...);
565+
shared_ptr<T> r;
566+
r.p = &rc->t;
567+
r.rc = rc.release();
568+
if constexpr (is_base_of_v<enable_shared_from_this<T>, T>) {
569+
r->weak_this = r;
570+
}
571+
return r;
572+
}
513573
}
514574

515575
} // namespace ctl

test/ctl/shared_ptr_test.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1717
// PERFORMANCE OF THIS SOFTWARE.
1818

19+
#include "ctl/is_same.h"
1920
#include "ctl/shared_ptr.h"
2021
#include "ctl/vector.h"
2122
#include "libc/mem/leaks.h"
@@ -88,6 +89,21 @@ class SharedThis : public enable_shared_from_this<SharedThis>
8889
class CanShareThis : public enable_shared_from_this<CanShareThis>
8990
{};
9091

92+
// Sample class used to demonstrate the CTL shared_ptr's weak_self feature.
93+
struct Tree : ctl::weak_self_base
94+
{
95+
ctl::shared_ptr<Tree> l, r;
96+
ctl::weak_ptr<Tree> p;
97+
Tree(ctl::weak_ptr<Tree> const& self, auto&& l2, auto&& r2)
98+
: l(ctl::forward<decltype(l2)>(l2)), r(ctl::forward<decltype(r2)>(r2))
99+
{
100+
if (l)
101+
l->p = self;
102+
if (r)
103+
r->p = self;
104+
}
105+
};
106+
91107
int
92108
main()
93109
{
@@ -276,6 +292,19 @@ main()
276292
return 25;
277293
}
278294

295+
if constexpr (ctl::is_same_v<shared_ptr<Tree>, ctl::shared_ptr<Tree>>) {
296+
// Exercise our off-STL make_shared with weak self support.
297+
auto t = ctl::make_shared<Tree>(
298+
ctl::make_shared<Tree>(ctl::make_shared<Tree>(nullptr, nullptr),
299+
nullptr),
300+
ctl::make_shared<Tree>(nullptr, nullptr));
301+
auto t2 = t->l->l->p.lock()->p.lock();
302+
if (t.owner_before(t2) || t2.owner_before(t))
303+
return 26;
304+
if (!t.owner_before(t->l) && !t->l.owner_before(t))
305+
return 27;
306+
}
307+
279308
// TODO(mrdomino): exercise threads / races. The reference count should be
280309
// atomically maintained.
281310

0 commit comments

Comments
 (0)