internal: new shared_ptr and weak_ptr implementation (#5883)
moves std::shared_ptrs to a new implementation Advantages: - you can dereference a weak_ptr directly. This will obviously segfault on a nullptr deref if it's expired. - this is useful to avoid the .lock() hell where we are 100% sure the pointer _should_ be valid. (and if it isn't, it should throw.) - weak_ptrs are still valid while the SP is being destroyed. - reasoning: while an object (e.g. CWindow) is being destroyed, its `weak_ptr self` should be accessible (the sp is still alive, and so is CWindow), but it's not because by stl it's already expired (to prevent resurrection) - this impl solves it differently. w_p is expired, but can still be dereferenced and used. Creating `s_p`s is not possible anymore, though. - this is useful in destructors and callbacks.
This commit is contained in:
parent
589f758d94
commit
1ed1ce9506
88 changed files with 899 additions and 414 deletions
300
src/helpers/memory/SharedPtr.hpp
Normal file
300
src/helpers/memory/SharedPtr.hpp
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
#pragma once
|
||||
|
||||
#include <typeinfo>
|
||||
#include <typeindex>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#define SP CSharedPointer
|
||||
|
||||
/*
|
||||
This is a custom impl of std::shared_ptr.
|
||||
It is not thread-safe like the STL one,
|
||||
but Hyprland is single-threaded anyways.
|
||||
|
||||
It differs a bit from how the STL one works,
|
||||
namely in the fact that it keeps the T* inside the
|
||||
control block, and that you can still make a CWeakPtr
|
||||
or deref an existing one inside the destructor.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
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)
|
||||
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<T> __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;
|
||||
}
|
||||
|
||||
virtual ~impl() {
|
||||
destroy();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class CSharedPointer {
|
||||
public:
|
||||
template <typename X>
|
||||
using validHierarchy = typename std::enable_if<std::is_assignable<T*, X*>::value>;
|
||||
|
||||
/* creates a new shared pointer managing a resource
|
||||
avoid calling. Could duplicate ownership. Prefer makeShared */
|
||||
explicit CSharedPointer(T* object) noexcept {
|
||||
impl_ = new CSharedPointer_::impl<T>(object);
|
||||
increment();
|
||||
}
|
||||
|
||||
/* creates a shared pointer from a reference */
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CSharedPointer(const CSharedPointer<U>& ref) noexcept {
|
||||
impl_ = ref.impl_;
|
||||
increment();
|
||||
}
|
||||
|
||||
CSharedPointer(const CSharedPointer& ref) noexcept {
|
||||
impl_ = ref.impl_;
|
||||
increment();
|
||||
}
|
||||
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CSharedPointer(CSharedPointer<U>&& ref) noexcept {
|
||||
std::swap(impl_, ref.impl_);
|
||||
}
|
||||
|
||||
CSharedPointer(CSharedPointer&& ref) noexcept {
|
||||
std::swap(impl_, ref.impl_);
|
||||
}
|
||||
|
||||
/* allows weakPointer to create from an impl */
|
||||
CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept {
|
||||
impl_ = implementation;
|
||||
increment();
|
||||
}
|
||||
|
||||
/* creates an empty shared pointer with no implementation */
|
||||
CSharedPointer() noexcept {
|
||||
; // empty
|
||||
}
|
||||
|
||||
/* creates an empty shared pointer with no implementation */
|
||||
CSharedPointer(std::nullptr_t) noexcept {
|
||||
; // empty
|
||||
}
|
||||
|
||||
~CSharedPointer() {
|
||||
// we do not decrement here,
|
||||
// because we want to preserve the pointer
|
||||
// in case this is the last owner.
|
||||
if (impl_ && impl_->ref() == 1)
|
||||
destroyImpl();
|
||||
else
|
||||
decrement();
|
||||
}
|
||||
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CSharedPointer<T>& operator=(const CSharedPointer<U>& rhs) {
|
||||
if (impl_ == rhs.impl_)
|
||||
return *this;
|
||||
|
||||
decrement();
|
||||
impl_ = rhs.impl_;
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSharedPointer& operator=(const CSharedPointer& rhs) {
|
||||
if (impl_ == rhs.impl_)
|
||||
return *this;
|
||||
|
||||
decrement();
|
||||
impl_ = rhs.impl_;
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CSharedPointer<T>& operator=(CSharedPointer<U>&& rhs) {
|
||||
std::swap(impl_, rhs.impl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSharedPointer& operator=(CSharedPointer&& rhs) {
|
||||
std::swap(impl_, rhs.impl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return impl_ && impl_->dataNonNull();
|
||||
}
|
||||
|
||||
bool operator==(const CSharedPointer& rhs) const {
|
||||
return impl_ == rhs.impl_;
|
||||
}
|
||||
|
||||
bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const {
|
||||
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
|
||||
}
|
||||
|
||||
bool operator<(const CSharedPointer& rhs) const {
|
||||
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
decrement();
|
||||
impl_ = nullptr;
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
|
||||
}
|
||||
|
||||
unsigned int strongRef() const {
|
||||
return impl_ ? impl_->ref() : 0;
|
||||
}
|
||||
|
||||
CSharedPointer_::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 <typename U, typename... Args>
|
||||
static CSharedPointer<U> makeShared(Args&&... args) {
|
||||
return CSharedPointer<U>(new U(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct std::hash<CSharedPointer<T>> {
|
||||
std::size_t operator()(const CSharedPointer<T>& p) const noexcept {
|
||||
return std::hash<void*>{}(p->impl_);
|
||||
}
|
||||
};
|
||||
188
src/helpers/memory/WeakPtr.hpp
Normal file
188
src/helpers/memory/WeakPtr.hpp
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
#pragma once
|
||||
|
||||
#include "SharedPtr.hpp"
|
||||
|
||||
#define WP CWeakPointer
|
||||
|
||||
/*
|
||||
This is a Hyprland implementation of std::weak_ptr.
|
||||
|
||||
See SharedPtr.hpp for more info on how it's different.
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
class CWeakPointer {
|
||||
public:
|
||||
template <typename X>
|
||||
using validHierarchy = typename std::enable_if<std::is_assignable<T*, X*>::value>;
|
||||
|
||||
/* create a weak ptr from a reference */
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CWeakPointer(const CSharedPointer<U>& ref) noexcept {
|
||||
if (!ref.impl_)
|
||||
return;
|
||||
|
||||
impl_ = ref.impl_;
|
||||
incrementWeak();
|
||||
}
|
||||
|
||||
/* create a weak ptr from another weak ptr */
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CWeakPointer(const CWeakPointer<U>& ref) noexcept {
|
||||
if (!ref.impl_)
|
||||
return;
|
||||
|
||||
impl_ = ref.impl_;
|
||||
incrementWeak();
|
||||
}
|
||||
|
||||
CWeakPointer(const CWeakPointer& ref) noexcept {
|
||||
if (!ref.impl_)
|
||||
return;
|
||||
|
||||
impl_ = ref.impl_;
|
||||
incrementWeak();
|
||||
}
|
||||
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CWeakPointer(CWeakPointer<U>&& ref) noexcept {
|
||||
std::swap(impl_, ref.impl_);
|
||||
}
|
||||
|
||||
CWeakPointer(CWeakPointer&& ref) noexcept {
|
||||
std::swap(impl_, ref.impl_);
|
||||
}
|
||||
|
||||
/* create a weak ptr from another weak ptr with assignment */
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CWeakPointer<T>& operator=(const CWeakPointer<U>& rhs) {
|
||||
if (impl_ == rhs.impl_)
|
||||
return *this;
|
||||
|
||||
decrementWeak();
|
||||
impl_ = rhs.impl_;
|
||||
incrementWeak();
|
||||
return *this;
|
||||
}
|
||||
|
||||
CWeakPointer<T>& operator=(const CWeakPointer& rhs) {
|
||||
if (impl_ == rhs.impl_)
|
||||
return *this;
|
||||
|
||||
decrementWeak();
|
||||
impl_ = rhs.impl_;
|
||||
incrementWeak();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* create a weak ptr from a shared ptr with assignment */
|
||||
template <typename U, typename = validHierarchy<U>>
|
||||
CWeakPointer<T>& operator=(const CSharedPointer<U>& rhs) {
|
||||
if ((uintptr_t)impl_ == (uintptr_t)rhs.impl_)
|
||||
return *this;
|
||||
|
||||
decrementWeak();
|
||||
impl_ = rhs.impl_;
|
||||
incrementWeak();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* create an empty weak ptr */
|
||||
CWeakPointer() {
|
||||
;
|
||||
}
|
||||
|
||||
~CWeakPointer() {
|
||||
decrementWeak();
|
||||
}
|
||||
|
||||
/* expired MAY return true even if the pointer is still stored.
|
||||
the situation would be e.g. self-weak pointer in a destructor.
|
||||
for pointer validity, use valid() */
|
||||
bool expired() const {
|
||||
return !impl_ || !impl_->dataNonNull() || impl_->destroying();
|
||||
}
|
||||
|
||||
/* this means the pointed-to object is not yet deleted and can still be
|
||||
referenced, but it might be in the process of being deleted.
|
||||
check !expired() if you want to check whether it's valid and
|
||||
assignable to a SP. */
|
||||
bool valid() const {
|
||||
return impl_ && impl_->dataNonNull();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
decrementWeak();
|
||||
impl_ = nullptr;
|
||||
}
|
||||
|
||||
CSharedPointer<T> lock() const {
|
||||
if (!impl_ || !impl_->dataNonNull() || impl_->destroying())
|
||||
return {};
|
||||
|
||||
return CSharedPointer<T>(impl_);
|
||||
}
|
||||
|
||||
/* this returns valid() */
|
||||
operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
|
||||
bool operator==(const CWeakPointer<T>& rhs) const {
|
||||
return impl_ == rhs.impl_;
|
||||
}
|
||||
|
||||
bool operator==(const CSharedPointer<T>& rhs) const {
|
||||
return impl_ == rhs.impl_;
|
||||
}
|
||||
|
||||
bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
|
||||
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
|
||||
}
|
||||
|
||||
bool operator<(const CWeakPointer& rhs) const {
|
||||
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
CSharedPointer_::impl_base* impl_ = nullptr;
|
||||
|
||||
private:
|
||||
/* no-op if there is no impl_ */
|
||||
void decrementWeak() {
|
||||
if (!impl_)
|
||||
return;
|
||||
|
||||
impl_->decWeak();
|
||||
|
||||
// we need to check for ->destroying,
|
||||
// because otherwise we could destroy here
|
||||
// and have a shared_ptr destroy the same thing
|
||||
// later (in situations where we have a weak_ptr to self)
|
||||
if (impl_->wref() == 0 && impl_->ref() == 0 && !impl_->destroying()) {
|
||||
delete impl_;
|
||||
impl_ = nullptr;
|
||||
}
|
||||
}
|
||||
/* no-op if there is no impl_ */
|
||||
void incrementWeak() {
|
||||
if (!impl_)
|
||||
return;
|
||||
|
||||
impl_->incWeak();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct std::hash<CWeakPointer<T>> {
|
||||
std::size_t operator()(const CWeakPointer<T>& p) const noexcept {
|
||||
return std::hash<void*>{}(p->impl_);
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue