ladybird/Libraries/LibGC/Weak.h
Andreas Kling 25a5ed94d6 LibGC: Add GC::Weak<T> as an alternative to AK::WeakPtr<T>
This is a weak pointer that integrates with the garbage collector.
It has a number of differences compared to AK::WeakPtr, including:

- The "control block" is allocated from a well-packed WeakBlock owned by
  the GC heap, not just a generic malloc allocation.

- Pointers to dead cells are nulled out by the garbage collector
  immediately before running destructors.

- It works on any GC::Cell derived type, meaning you don't have to
  inherit from AK::Weakable for the ability to be weakly referenced.

- The Weak always points to a control block, even when "null" (it then
  points to a null WeakImpl), which means one less null check when
  chasing pointers.
2025-10-17 17:22:16 +02:00

166 lines
3.5 KiB
C++

/*
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Badge.h>
#include <AK/RefPtr.h>
#include <LibGC/Export.h>
#include <LibGC/Ptr.h>
namespace GC {
class WeakBlock;
class WeakImpl {
public:
// NOTE: Null GC::Weaks point at this WeakImpl. This allows Weak to always chase the impl pointer without null-checking it.
static GC_API WeakImpl the_null_weak_impl;
WeakImpl() = default;
WeakImpl(void* ptr)
: m_ptr(ptr)
{
}
void* ptr() const { return m_ptr; }
void set_ptr(Badge<WeakBlock>, void* ptr) { m_ptr = ptr; }
bool operator==(WeakImpl const& other) const { return m_ptr == other.m_ptr; }
bool operator!=(WeakImpl const& other) const { return m_ptr != other.m_ptr; }
void ref() const { ++m_ref_count; }
void unref() const
{
VERIFY(m_ref_count);
--m_ref_count;
}
size_t ref_count() const { return m_ref_count; }
enum class State {
Allocated,
Freelist,
};
void set_state(State state) { m_state = state; }
State state() const { return m_state; }
private:
mutable size_t m_ref_count { 0 };
State m_state { State::Allocated };
void* m_ptr { nullptr };
};
template<typename T>
class Weak {
public:
constexpr Weak() = default;
Weak(nullptr_t) { }
Weak(T const* ptr);
Weak(T const& ptr);
template<typename U>
Weak(Weak<U> const& other)
requires(IsConvertible<U*, T*>);
Weak(Ref<T> const& other);
template<typename U>
Weak(Ref<U> const& other)
requires(IsConvertible<U*, T*>);
template<typename U>
Weak& operator=(Weak<U> const& other)
requires(IsConvertible<U*, T*>)
{
m_impl = other.impl();
return *this;
}
Weak& operator=(Ref<T> const& other);
template<typename U>
Weak& operator=(Ref<U> const& other)
requires(IsConvertible<U*, T*>);
Weak& operator=(T const& other);
template<typename U>
Weak& operator=(U const& other)
requires(IsConvertible<U*, T*>);
Weak& operator=(T const* other);
template<typename U>
Weak& operator=(U const* other)
requires(IsConvertible<U*, T*>);
T* operator->() const
{
ASSERT(ptr());
return ptr();
}
[[nodiscard]] T& operator*() const
{
ASSERT(ptr());
return *ptr();
}
Ptr<T> ptr() const { return static_cast<T*>(impl().ptr()); }
explicit operator bool() const { return !!ptr(); }
bool operator!() const { return !ptr(); }
operator T*() const { return ptr(); }
Ref<T> as_nonnull() const
{
ASSERT(ptr());
return *ptr();
}
WeakImpl& impl() const { return *m_impl; }
private:
NonnullRefPtr<WeakImpl> m_impl { WeakImpl::the_null_weak_impl };
};
template<typename T, typename U>
inline bool operator==(Weak<T> const& a, Ptr<U> const& b)
{
return a.ptr() == b.ptr();
}
template<typename T, typename U>
inline bool operator==(Weak<T> const& a, Ref<U> const& b)
{
return a.ptr() == b.ptr();
}
}
namespace AK {
template<typename T>
struct Traits<GC::Weak<T>> : public DefaultTraits<GC::Weak<T>> {
static unsigned hash(GC::Weak<T> const& value)
{
return Traits<T*>::hash(value.ptr());
}
};
template<typename T>
struct Formatter<GC::Weak<T>> : Formatter<T const*> {
ErrorOr<void> format(FormatBuilder& builder, GC::Weak<T> const& value)
{
return Formatter<T const*>::format(builder, value.ptr());
}
};
}