Commit 58070f85 authored by Kenton Varda's avatar Kenton Varda

Implement Own<T>::attach() for combining objects with their dependencies.

parent ef130da3
......@@ -65,6 +65,47 @@ TEST(Memory, AssignNested) {
EXPECT_TRUE(destroyed1 && destroyed2);
}
struct DestructionOrderRecorder {
DestructionOrderRecorder(uint& counter, uint& recordTo)
: counter(counter), recordTo(recordTo) {}
~DestructionOrderRecorder() {
recordTo = ++counter;
}
uint& counter;
uint& recordTo;
};
TEST(Memory, Attach) {
uint counter = 0;
uint destroyed1 = 0;
uint destroyed2 = 0;
uint destroyed3 = 0;
auto obj1 = kj::heap<DestructionOrderRecorder>(counter, destroyed1);
auto obj2 = kj::heap<DestructionOrderRecorder>(counter, destroyed2);
auto obj3 = kj::heap<DestructionOrderRecorder>(counter, destroyed3);
auto ptr = obj1.get();
Own<DestructionOrderRecorder> combined = obj1.attach(kj::mv(obj2), kj::mv(obj3));
KJ_EXPECT(combined.get() == ptr);
KJ_EXPECT(obj1.get() == nullptr);
KJ_EXPECT(obj2.get() == nullptr);
KJ_EXPECT(obj3.get() == nullptr);
KJ_EXPECT(destroyed1 == 0);
KJ_EXPECT(destroyed2 == 0);
KJ_EXPECT(destroyed3 == 0);
combined = nullptr;
KJ_EXPECT(destroyed1 == 1, destroyed1);
KJ_EXPECT(destroyed2 == 2, destroyed2);
KJ_EXPECT(destroyed3 == 3, destroyed3);
}
// TODO(test): More tests.
} // namespace
......
......@@ -150,6 +150,12 @@ public:
return *this;
}
template <typename... Attachments>
Own<T> attach(Attachments&&... attachments);
// Returns an Own<T> which points to the same object but which also ensures that all values
// passed to `attachments` remain alive until after this object is destroyed. Normally
// `attachments` are other Own<?>s pointing to objects that this one depends on.
template <typename U>
Own<U> downcast() {
// Downcast the pointer to Own<U>, destroying the original pointer. If this pointer does not
......@@ -401,6 +407,50 @@ void Disposer::dispose(T* object) const {
Dispose_<T>::dispose(object, *this);
}
namespace _ { // private
template <typename... T>
struct OwnedBundle;
template <>
struct OwnedBundle<> {};
template <typename First, typename... Rest>
struct OwnedBundle<First, Rest...>: public OwnedBundle<Rest...> {
OwnedBundle(First&& first, Rest&&... rest)
: OwnedBundle<Rest...>(kj::fwd<Rest>(rest)...), first(kj::fwd<First>(first)) {}
// Note that it's intentional that `first` is destroyed before `rest`. This way, doing
// ptr.attach(foo, bar, baz) is equivalent to ptr.attach(foo).attach(bar).attach(baz) in terms
// of destruction order (although the former does fewer allocations).
Decay<First> first;
};
template <typename... T>
struct DisposableOwnedBundle: public Disposer, public OwnedBundle<T...> {
DisposableOwnedBundle(T&&... values): OwnedBundle<T...>(kj::fwd<T>(values)...) {}
void disposeImpl(void* pointer) const override { delete this; }
};
} // namespace _ (private)
template <typename T>
template <typename... Attachments>
Own<T> Own<T>::attach(Attachments&&... attachments) {
T* ptrCopy = ptr;
KJ_IREQUIRE(ptrCopy != nullptr, "cannot attach to null pointer");
// HACK: If someone accidentally calls .attach() on a null pointer in opt mode, try our best to
// accomplish reasonable behavior: We turn the pointer non-null but still invalid, so that the
// disposer will still be called when the pointer goes out of scope.
if (ptrCopy == nullptr) ptrCopy = reinterpret_cast<T*>(1);
auto bundle = new _::DisposableOwnedBundle<Own<T>, Attachments...>(
kj::mv(*this), kj::fwd<Attachments>(attachments)...);
return Own<T>(ptrCopy, *bundle);
}
} // namespace kj
#endif // KJ_MEMORY_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment