/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: SafeDestroy.hpp Date: 2022-07-19 Author: Reece ***/ #pragma once namespace AuUtil { namespace Detail { template struct AuHasDestroy { template static constexpr AuTrueType Test(decltype(&C::DestroyPrivate)); template static constexpr AuFalseType Test(...); using type = decltype(Test(0)); }; template struct AuHasDeconstruct { template static constexpr AuTrueType Test(decltype(&C::DeconstructPrivate)); template static constexpr AuFalseType Test(...); using type = decltype(Test(0)); }; template struct AuHasPreDeconstruct { template static constexpr AuTrueType Test(decltype(&C::PreDeconstructPrivate)); template static constexpr AuFalseType Test(...); using type = decltype(Test(0)); }; template constexpr inline bool AuHasDestroy_v = AuHasDestroy::type::value; template constexpr inline bool AuHasDesconstruct_v = AuHasDeconstruct::type::value; template constexpr inline bool AuHasPreDesconstruct_v = AuHasPreDeconstruct::type::value; } #define AU_SAFE_DESTROY(type) \ public: \ virtual ~type() \ { \ if (AuAtomicTestAndSet(&this->uCurrentState_, 2)) \ { \ return; \ } \ \ type :: _TryPreDeconstruct(this); \ \ this->Destroy(); \ \ type :: _TryDeconstruct(this); \ \ } \ \ inline void Destroy() \ { \ if (AuAtomicTestAndSet(&this->uCurrentState_, 1)) \ { \ return; \ } \ \ type :: _TryDeconstructPrivate(this); \ } \ \ friend AuUtil::Detail::AuHasDestroy; \ friend AuUtil::Detail::AuHasDeconstruct; \ friend AuUtil::Detail::AuHasPreDeconstruct; \ \ private: \ AuUInt32 uCurrentState_{}; \ \ template \ static void _TryPreDeconstruct(T *that) \ { \ if constexpr (AuUtil::Detail::AuHasPreDesconstruct_v) \ { \ that->PreDeconstructPrivate(); \ } \ } \ \ template \ static void _TryDeconstruct(T *that) \ { \ if constexpr (AuUtil::Detail::AuHasDesconstruct_v) \ { \ that->DeconstructPrivate(); \ } \ } \ \ template \ static void _TryDeconstructPrivate(T *that) \ { \ if constexpr (AuUtil::Detail::AuHasDestroy_v) \ { \ that->DestroyPrivate(); \ } \ } \ \ public: } /** Safe destroy objects: * implement a builtin ::Destroy function for objects whose ownership is expectantly shared instead of explicitly owned (eg: overlapped io resources over simplier utility class objects with explicit an release function) * allow the user to define ::DestroyPrivate, ::DeconstructPrivate, and ::*Pre*DeconstructPrivate methods in their class * forces virtual destruction to prevent hidden leaks note: The real reason we do this is bc "some projects" expect the destruction implementation itself to be a virtual destructor (an implicitly overloaded virtual, if it were a regular method). That way API objects can call a `virtual ~IInterface()` and nuke safely nuke away the underlying safe-destroy object by using the builtin "everyone has this destruct method" destructor; bypassing the need for a `virtual Destroy() = 0;` and alternative an AU_SAFE_DESTROY. * enable safe explicit double ~Type() support; however, some objects in your STL may not support this behaviour (eg: AuFunction) workaround: implement explicit ::Deconstruct() with AuResetMember calls to fixup the UB * allows for an arbitrary amount of Type::Destroy() and Type::~Type() calls across multiple threads Usage: Defines #define AU_SAFE_DESTROY(type) Simply add AU_SAFE_DESTROY to the types body add arbitrarily add the following callback methods as needed void DestroyPrivate(void) - Generic called once object destroy void DeconstructPrivate(void) - Object destruction hook. Called once. Called After DestroyPrivate. void PreDeconstructPrivate(void) - Object destruction hook. Called once. Called before DestroyPrivate. These user defined functions may be private and/or virtual, if implemented at all. */