memory: add CAtomicSharedPointer and CAtomicWeakPointer

This commit is contained in:
Maximilian Seidler 2025-06-23 08:00:38 +02:00
parent 38f3a21165
commit 38e3801cda
3 changed files with 426 additions and 2 deletions

2
.gitignore vendored
View file

@ -34,6 +34,7 @@
build/
.vscode/
.cache/
.direnv/
.cmake/
CMakeCache.txt
@ -44,3 +45,4 @@ Makefile
cmake_install.cmake
compile_commands.json
hyprutils.pc
.envrc

View file

@ -0,0 +1,334 @@
#pragma once
#include "./ImplBase.hpp"
#include "./SharedPtr.hpp"
#include "./WeakPtr.hpp"
#include <mutex>
namespace Hyprutils::Memory {
namespace Atomic_ {
template <typename T>
class impl : public Impl_::impl<T> {
std::recursive_mutex m_mutex;
public:
impl(T* data, bool lock = true) noexcept : Impl_::impl<T>(data, lock) {
;
}
std::lock_guard<std::recursive_mutex> lockGuard() {
return std::lock_guard<std::recursive_mutex>(m_mutex);
}
};
}
template <typename T>
class CAtomicSharedPointer {
template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CAtomicSharedPointer<T>&, X>::value, CAtomicSharedPointer&>::type;
public:
explicit CAtomicSharedPointer(Impl_::impl_base* impl) noexcept : m_ptr(impl) {}
CAtomicSharedPointer(const CAtomicSharedPointer<T>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicSharedPointer(const CAtomicSharedPointer<U>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicSharedPointer(CAtomicSharedPointer<U>&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicSharedPointer(CAtomicSharedPointer&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicSharedPointer() noexcept {
; // empty
}
/* creates an empty shared pointer with no implementation */
CAtomicSharedPointer(std::nullptr_t) noexcept {
; // empty
}
~CAtomicSharedPointer() {
if (!m_ptr.impl_)
return;
auto lg = implLockGuard();
m_ptr.reset();
}
template <typename U>
validHierarchy<const CAtomicSharedPointer<U>&> operator=(const CAtomicSharedPointer<U>& rhs) {
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
if (!rhs.m_ptr.impl_)
return *this;
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
CAtomicSharedPointer& operator=(const CAtomicSharedPointer& rhs) {
if (this == &rhs)
return *this;
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
if (!rhs.m_ptr.impl_)
return *this;
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
template <typename U>
validHierarchy<const CAtomicSharedPointer<U>&> operator=(CAtomicSharedPointer<U>&& rhs) noexcept {
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
CAtomicSharedPointer& operator=(CAtomicSharedPointer&& rhs) noexcept {
if (this == &rhs)
return *this;
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
void reset() {
if (!m_ptr.impl_)
return;
auto lg = implLockGuard();
m_ptr.reset();
}
T& operator*() const {
return *m_ptr;
}
T* operator->() const {
return m_ptr.get();
}
T* get() const {
return m_ptr.get();
}
operator bool() const {
return m_ptr;
}
bool operator==(const CAtomicSharedPointer& rhs) const {
return m_ptr == rhs.m_ptr;
}
bool operator()(const CAtomicSharedPointer& lhs, const CAtomicSharedPointer& rhs) const {
return lhs.m_ptr == rhs.m_ptr;
}
unsigned int strongRef() const {
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
}
std::lock_guard<std::recursive_mutex> implLockGuard() const {
return ((Atomic_::impl<T>*)m_ptr.impl_)->lockGuard();
}
CSharedPointer<T> m_ptr;
};
template <typename T>
class CAtomicWeakPointer {
template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CAtomicWeakPointer<T>&, X>::value, CAtomicWeakPointer&>::type;
public:
CAtomicWeakPointer(const CAtomicWeakPointer<T>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicWeakPointer(const CAtomicWeakPointer<U>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicWeakPointer(CAtomicWeakPointer<U>&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicWeakPointer(CAtomicWeakPointer&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicWeakPointer(const CAtomicSharedPointer<T>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
CAtomicWeakPointer() noexcept {
; // empty
}
/* creates an empty shared pointer with no implementation */
CAtomicWeakPointer(std::nullptr_t) noexcept {
; // empty
}
~CAtomicWeakPointer() {
if (!m_ptr.impl_)
return;
auto lg = implLockGuard();
m_ptr.reset();
}
template <typename U>
validHierarchy<const CAtomicWeakPointer<U>&> operator=(const CAtomicWeakPointer<U>& rhs) {
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
CAtomicWeakPointer& operator=(const CAtomicWeakPointer& rhs) {
if (this == &rhs)
return *this;
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
template <typename U>
validHierarchy<const CAtomicWeakPointer<U>&> operator=(CAtomicWeakPointer<U>&& rhs) noexcept {
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
CAtomicWeakPointer& operator=(CAtomicWeakPointer&& rhs) noexcept {
if (this == &rhs)
return *this;
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
void reset() {
if (!m_ptr.impl_)
return;
auto lg = implLockGuard();
m_ptr.reset();
}
T& operator*() const {
return *m_ptr;
}
T* operator->() const {
return m_ptr.get();
}
T* get() const {
return m_ptr.get();
}
operator bool() const {
return m_ptr;
}
bool operator==(const CAtomicWeakPointer& rhs) const {
return m_ptr == rhs.m_ptr;
}
bool operator==(const CAtomicSharedPointer<T>& rhs) const {
return m_ptr == rhs.m_ptr;
}
bool operator()(const CAtomicWeakPointer& lhs, const CAtomicWeakPointer& rhs) const {
return lhs.m_ptr == rhs.m_ptr;
}
bool expired() {
return m_ptr.expired();
}
bool valid() {
return m_ptr.valid();
}
CAtomicSharedPointer<T> lock() const {
if (!m_ptr.impl_)
return {};
auto lg = implLockGuard();
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
return {};
return CAtomicSharedPointer<T>(m_ptr.impl_);
}
std::lock_guard<std::recursive_mutex> implLockGuard() const {
return ((Atomic_::impl<T>*)m_ptr.impl_)->lockGuard();
}
CWeakPointer<T> m_ptr;
};
template <typename U, typename... Args>
static CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) {
return CAtomicSharedPointer<U>(new Atomic_::impl<U>(new U(std::forward<Args>(args)...)));
}
}

View file

@ -1,6 +1,11 @@
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include "shared.hpp"
#include <chrono>
#include <print>
#include <thread>
#include <vector>
using namespace Hyprutils::Memory;
@ -9,6 +14,87 @@ using namespace Hyprutils::Memory;
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer
#define AWP CAtomicWeakPointer
#define NTHREADS 8
#define ITERATIONS 10000
static int testAtomicImpl() {
int ret = 0;
{
// Using makeShared here could lead to invalid refcounts.
ASP<int> shared = makeAtomicShared<int>(0);
std::vector<std::thread> threads;
threads.reserve(NTHREADS);
for (size_t i = 0; i < NTHREADS; i++) {
threads.emplace_back([shared]() {
for (size_t j = 0; j < ITERATIONS; j++) {
ASP<int> strongRef = shared;
(*shared)++;
strongRef.reset();
}
});
}
for (auto& thread : threads) {
thread.join();
}
// Actual count is not incremented in a thread-safe manner here, so we can't check it.
// We just want to check that the concurent refcounting doesn't cause any memory corruption.
shared.reset();
EXPECT(shared, false);
}
{
ASP<int> shared = makeAtomicShared<int>(0);
AWP<int> weak = shared;
std::vector<std::thread> threads;
threads.reserve(NTHREADS);
for (size_t i = 0; i < NTHREADS; i++) {
threads.emplace_back([weak]() {
for (size_t j = 0; j < ITERATIONS; j++) {
if (auto s = weak.lock(); s) {
(*s)++;
}
}
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
shared.reset();
for (auto& thread : threads) {
thread.join();
}
EXPECT(shared.strongRef(), 0);
EXPECT(weak.valid(), false);
auto shared2 = weak.lock();
EXPECT(shared, false);
EXPECT(shared2.get(), nullptr);
EXPECT(shared.strongRef(), 0);
EXPECT(weak.valid(), false);
EXPECT(weak.expired(), true);
}
{ // This tests recursive deletion. When foo will be deleted, bar will be deleted within the foo dtor.
class CFoo {
public:
AWP<CFoo> bar;
};
ASP<CFoo> foo = makeAtomicShared<CFoo>();
foo->bar = foo;
}
return ret;
}
int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr2 = makeShared<int>(-1337);
@ -62,5 +148,7 @@ int main(int argc, char** argv, char** envp) {
EXPECT(*intPtr2AsUint, 10);
EXPECT(*intPtr2, 10);
EXPECT(testAtomicImpl(), 0);
return ret;
}
}