From 423c69d697f56af4f8f2de7e2279eead17901228 Mon Sep 17 00:00:00 2001 From: vaxerski Date: Thu, 23 Jan 2025 11:34:35 +0000 Subject: [PATCH] memory: implement a unique pointer this unique pointer differs from the STL, because it allows weak pointers to it, which cannot be lock()ed. under the hood, it's just a SP that cannot be referenced. --- include/hyprutils/memory/ImplBase.hpp | 119 ++++++++++++++++++++ include/hyprutils/memory/SharedPtr.hpp | 113 +------------------ include/hyprutils/memory/UniquePtr.hpp | 149 +++++++++++++++++++++++++ include/hyprutils/memory/WeakPtr.hpp | 19 +++- tests/memory.cpp | 23 +++- 5 files changed, 308 insertions(+), 115 deletions(-) create mode 100644 include/hyprutils/memory/ImplBase.hpp create mode 100644 include/hyprutils/memory/UniquePtr.hpp diff --git a/include/hyprutils/memory/ImplBase.hpp b/include/hyprutils/memory/ImplBase.hpp new file mode 100644 index 0000000..9b9de54 --- /dev/null +++ b/include/hyprutils/memory/ImplBase.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include + +namespace Hyprutils { + namespace Memory { + namespace Impl_ { + class impl_base { + public: + virtual ~impl_base() {}; + + virtual void inc() noexcept = 0; + virtual void dec() noexcept = 0; + virtual void incWeak() noexcept = 0; + virtual void decWeak() noexcept = 0; + virtual unsigned int ref() noexcept = 0; + virtual unsigned int wref() noexcept = 0; + virtual void destroy() noexcept = 0; + virtual bool destroying() noexcept = 0; + virtual bool dataNonNull() noexcept = 0; + virtual bool lockable() noexcept = 0; + virtual void* getData() noexcept = 0; + }; + + template + class impl : public impl_base { + public: + impl(T* data, bool lock = true) noexcept : _data(data), _lockable(lock) { + ; + } + + /* strong refcount */ + unsigned int _ref = 0; + /* weak refcount */ + unsigned int _weak = 0; + /* if this is lockable (shared) */ + bool _lockable = true; + + T* _data = nullptr; + + friend void swap(impl*& a, impl*& b) { + impl* tmp = a; + a = b; + b = tmp; + } + + /* if the destructor was called, + creating shared_ptrs is no longer valid */ + bool _destroying = false; + + void _destroy() { + if (!_data || _destroying) + return; + + // first, we destroy the data, but keep the pointer. + // this way, weak pointers will still be able to + // reference and use, but no longer create shared ones. + _destroying = true; + __deleter(_data); + // now, we can reset the data and call it a day. + _data = nullptr; + _destroying = false; + } + + std::default_delete __deleter{}; + + // + virtual void inc() noexcept { + _ref++; + } + + virtual void dec() noexcept { + _ref--; + } + + virtual void incWeak() noexcept { + _weak++; + } + + virtual void decWeak() noexcept { + _weak--; + } + + virtual unsigned int ref() noexcept { + return _ref; + } + + virtual unsigned int wref() noexcept { + return _weak; + } + + virtual void destroy() noexcept { + _destroy(); + } + + virtual bool destroying() noexcept { + return _destroying; + } + + virtual bool lockable() noexcept { + return _lockable; + } + + virtual bool dataNonNull() noexcept { + return _data != nullptr; + } + + virtual void* getData() noexcept { + return _data; + } + + virtual ~impl() { + destroy(); + } + }; + } + } + +} \ No newline at end of file diff --git a/include/hyprutils/memory/SharedPtr.hpp b/include/hyprutils/memory/SharedPtr.hpp index 81ea705..d2c5995 100644 --- a/include/hyprutils/memory/SharedPtr.hpp +++ b/include/hyprutils/memory/SharedPtr.hpp @@ -1,8 +1,6 @@ #pragma once -#include -#include -#include +#include "ImplBase.hpp" /* This is a custom impl of std::shared_ptr. @@ -17,109 +15,6 @@ namespace Hyprutils { namespace Memory { - namespace CSharedPointer_ { - class impl_base { - public: - virtual ~impl_base() {}; - - virtual void inc() noexcept = 0; - virtual void dec() noexcept = 0; - virtual void incWeak() noexcept = 0; - virtual void decWeak() noexcept = 0; - virtual unsigned int ref() noexcept = 0; - virtual unsigned int wref() noexcept = 0; - virtual void destroy() noexcept = 0; - virtual bool destroying() noexcept = 0; - virtual bool dataNonNull() noexcept = 0; - virtual void* getData() noexcept = 0; - }; - - template - class impl : public impl_base { - public: - impl(T* data) noexcept : _data(data) { - ; - } - - /* strong refcount */ - unsigned int _ref = 0; - /* weak refcount */ - unsigned int _weak = 0; - - T* _data = nullptr; - - friend void swap(impl*& a, impl*& b) { - impl* tmp = a; - a = b; - b = tmp; - } - - /* if the destructor was called, - creating shared_ptrs is no longer valid */ - bool _destroying = false; - - void _destroy() { - if (!_data || _destroying) - return; - - // first, we destroy the data, but keep the pointer. - // this way, weak pointers will still be able to - // reference and use, but no longer create shared ones. - _destroying = true; - __deleter(_data); - // now, we can reset the data and call it a day. - _data = nullptr; - _destroying = false; - } - - std::default_delete __deleter{}; - - // - virtual void inc() noexcept { - _ref++; - } - - virtual void dec() noexcept { - _ref--; - } - - virtual void incWeak() noexcept { - _weak++; - } - - virtual void decWeak() noexcept { - _weak--; - } - - virtual unsigned int ref() noexcept { - return _ref; - } - - virtual unsigned int wref() noexcept { - return _weak; - } - - virtual void destroy() noexcept { - _destroy(); - } - - virtual bool destroying() noexcept { - return _destroying; - } - - virtual bool dataNonNull() noexcept { - return _data != nullptr; - } - - virtual void* getData() noexcept { - return _data; - } - - virtual ~impl() { - destroy(); - } - }; - }; template class CSharedPointer { @@ -132,7 +27,7 @@ namespace Hyprutils { /* creates a new shared pointer managing a resource avoid calling. Could duplicate ownership. Prefer makeShared */ explicit CSharedPointer(T* object) noexcept { - impl_ = new CSharedPointer_::impl(object); + impl_ = new Impl_::impl(object); increment(); } @@ -158,7 +53,7 @@ namespace Hyprutils { } /* allows weakPointer to create from an impl */ - CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept { + CSharedPointer(Impl_::impl_base* implementation) noexcept { impl_ = implementation; increment(); } @@ -246,7 +141,7 @@ namespace Hyprutils { return impl_ ? impl_->ref() : 0; } - CSharedPointer_::impl_base* impl_ = nullptr; + Impl_::impl_base* impl_ = nullptr; private: /* diff --git a/include/hyprutils/memory/UniquePtr.hpp b/include/hyprutils/memory/UniquePtr.hpp new file mode 100644 index 0000000..8588560 --- /dev/null +++ b/include/hyprutils/memory/UniquePtr.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include "ImplBase.hpp" + +/* + This is a custom impl of std::unique_ptr. + In contrast to the STL one, it allows for + creation of a weak_ptr, that will then be unable + to be locked. +*/ + +namespace Hyprutils { + namespace Memory { + template + class CUniquePointer { + public: + template + using validHierarchy = typename std::enable_if&, X>::value, CUniquePointer&>::type; + template + using isConstructible = typename std::enable_if::value>::type; + + /* creates a new unique pointer managing a resource + avoid calling. Could duplicate ownership. Prefer makeUnique */ + explicit CUniquePointer(T* object) noexcept { + impl_ = new Impl_::impl(object, false); + increment(); + } + + /* creates a shared pointer from a reference */ + template > + CUniquePointer(const CUniquePointer& ref) = delete; + CUniquePointer(const CUniquePointer& ref) = delete; + + template > + CUniquePointer(CUniquePointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + CUniquePointer(CUniquePointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + /* creates an empty unique pointer with no implementation */ + CUniquePointer() noexcept { + ; // empty + } + + /* creates an empty unique pointer with no implementation */ + CUniquePointer(std::nullptr_t) noexcept { + ; // empty + } + + ~CUniquePointer() { + decrement(); + } + + template + validHierarchy&> operator=(const CUniquePointer& rhs) = delete; + CUniquePointer& operator=(const CUniquePointer& rhs) = delete; + + template + validHierarchy&> operator=(CUniquePointer&& rhs) { + std::swap(impl_, rhs.impl_); + return *this; + } + + CUniquePointer& operator=(CUniquePointer&& rhs) { + std::swap(impl_, rhs.impl_); + return *this; + } + + operator bool() const { + return impl_; + } + + bool operator()(const CUniquePointer& lhs, const CUniquePointer& rhs) const { + return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); + } + + T* operator->() const { + return get(); + } + + T& operator*() const { + return *get(); + } + + void reset() { + decrement(); + impl_ = nullptr; + } + + T* get() const { + return impl_ ? static_cast(impl_->getData()) : nullptr; + } + + Impl_::impl_base* impl_ = nullptr; + + private: + /* + no-op if there is no impl_ + may delete the stored object if ref == 0 + may delete and reset impl_ if ref == 0 and weak == 0 + */ + void decrement() { + if (!impl_) + return; + + impl_->dec(); + + // if ref == 0, we can destroy impl + if (impl_->ref() == 0) + destroyImpl(); + } + /* no-op if there is no impl_ */ + void increment() { + if (!impl_) + return; + + impl_->inc(); + } + + /* destroy the pointed-to object + if able, will also destroy impl */ + void destroyImpl() { + // destroy the impl contents + impl_->destroy(); + + // check for weak refs, if zero, we can also delete impl_ + if (impl_->wref() == 0) { + delete impl_; + impl_ = nullptr; + } + } + }; + + template + static CUniquePointer makeUnique(Args&&... args) { + return CUniquePointer(new U(std::forward(args)...)); + } + } +} + +template +struct std::hash> { + std::size_t operator()(const Hyprutils::Memory::CUniquePointer& p) const noexcept { + return std::hash{}(p.impl_); + } +}; \ No newline at end of file diff --git a/include/hyprutils/memory/WeakPtr.hpp b/include/hyprutils/memory/WeakPtr.hpp index cd0d4bd..5617214 100644 --- a/include/hyprutils/memory/WeakPtr.hpp +++ b/include/hyprutils/memory/WeakPtr.hpp @@ -1,6 +1,7 @@ #pragma once #include "./SharedPtr.hpp" +#include "./UniquePtr.hpp" /* This is a Hyprland implementation of std::weak_ptr. @@ -28,6 +29,16 @@ namespace Hyprutils { incrementWeak(); } + /* create a weak ptr from a reference */ + template > + CWeakPointer(const CUniquePointer& ref) noexcept { + if (!ref.impl_) + return; + + impl_ = ref.impl_; + incrementWeak(); + } + /* create a weak ptr from another weak ptr */ template > CWeakPointer(const CWeakPointer& ref) noexcept { @@ -119,7 +130,7 @@ namespace Hyprutils { } CSharedPointer lock() const { - if (!impl_ || !impl_->dataNonNull() || impl_->destroying()) + if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable()) return {}; return CSharedPointer(impl_); @@ -154,7 +165,11 @@ namespace Hyprutils { return get(); } - CSharedPointer_::impl_base* impl_ = nullptr; + T& operator*() const { + return *get(); + } + + Impl_::impl_base* impl_ = nullptr; private: /* no-op if there is no impl_ */ diff --git a/tests/memory.cpp b/tests/memory.cpp index e0ac822..2c43303 100644 --- a/tests/memory.cpp +++ b/tests/memory.cpp @@ -1,4 +1,5 @@ #include +#include #include "shared.hpp" #include @@ -6,22 +7,32 @@ using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer +#define UP CUniquePointer int main(int argc, char** argv, char** envp) { - SP intPtr = makeShared(10); - SP intPtr2 = makeShared(1337); + SP intPtr = makeShared(10); + SP intPtr2 = makeShared(1337); + UP intUnique = makeUnique(420); int ret = 0; EXPECT(*intPtr, 10); EXPECT(intPtr.strongRef(), 1); + EXPECT(*intUnique, 420); - WP weak = intPtr; + WP weak = intPtr; + WP weakUnique = intUnique; EXPECT(*intPtr, 10); EXPECT(intPtr.strongRef(), 1); - EXPECT(*weak.get(), 10); + EXPECT(*weak, 10); EXPECT(weak.expired(), false); + EXPECT(*weakUnique, 420); + EXPECT(weakUnique.expired(), false); + EXPECT(intUnique.impl_->wref(), 1); + + SP sharedFromUnique = weakUnique.lock(); + EXPECT(sharedFromUnique, nullptr); std::vector> sps; sps.push_back(intPtr); @@ -31,11 +42,15 @@ int main(int argc, char** argv, char** envp) { std::erase_if(sps, [intPtr](const auto& e) { return e == intPtr; }); intPtr.reset(); + intUnique.reset(); EXPECT(weak.impl_->ref(), 0); + EXPECT(weakUnique.impl_->ref(), 0); + EXPECT(weakUnique.impl_->wref(), 1); EXPECT(intPtr2.strongRef(), 3); EXPECT(weak.expired(), true); + EXPECT(weakUnique.expired(), true); return ret; } \ No newline at end of file