// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4365 4987) #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif namespace pma { template struct New { template B* operator()(Args&& ... args) { return new T{std::forward(args)...}; } }; template struct Delete { void operator()(B* ptr) { // Calling delete on an incomplete type is undefined behavior. // This check will result in a compile error for incomplete types, rather than allow UB. #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" #endif using complete_type_checker = char[sizeof(T) ? 1 : -1]; #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop #endif static_cast(sizeof(complete_type_checker)); delete ptr; } }; template struct New { T* operator()(std::size_t size) { return new T[size]{}; } }; template struct Delete { void operator()(T* ptr) { // Calling delete on an incomplete type is undefined behavior. // This check will result in a compile error for incomplete types, rather than allow UB. #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" #endif using complete_type_checker = char[sizeof(T) ? 1 : -1]; #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop #endif static_cast(sizeof(complete_type_checker)); delete[] ptr; } }; template struct FactoryCreate { template B* operator()(Args&& ... args) { return T::create(std::forward(args)...); } }; template struct FactoryDestroy { void operator()(B* ptr) { T::destroy(static_cast(ptr)); } }; template struct DefaultInstanceCreator { using type = New; }; template struct DefaultInstanceDestroyer { using type = Delete; }; /** @brief Takes ownership over the given pointer and handles it's lifetime. @note As ScopedPtr inherits the specified destroyer type, stateless lifetime managers are zero-cost, but it's also possible to use stateful lifetime managers (such as lambdas with captures and what-not). For stateful lifetime managers, a dedicated constructor exists that receives the destroyer instance and initializes the inherited destroyer type with it. @see makeScoped @see New @see Delete @see FactoryCreate @see FactoryDestroy @see DefaultInstanceCreator @see DefaultInstanceDestroyer */ template::type> class ScopedPtr : private TDestroyer { private: template struct inspect { using element_type = U; using pointer_type = element_type*; using is_array = std::false_type; }; template struct inspect { using element_type = U; using pointer_type = element_type*; using is_array = std::true_type; }; public: using pointer = typename inspect::pointer_type; using element_type = typename inspect::element_type; using destroyer_type = TDestroyer; template friend class ScopedPtr; public: ScopedPtr() : ptr{nullptr} { } explicit ScopedPtr(pointer ptr_) : ptr{ptr_} { } ScopedPtr(pointer ptr_, destroyer_type&& destroyer) : destroyer_type{std::move(destroyer)}, ptr{ptr_} { } ~ScopedPtr() { if (ptr) { destroyer_type::operator()(ptr); ptr = pointer{}; } } ScopedPtr(std::nullptr_t) : ptr{nullptr} { } ScopedPtr& operator=(std::nullptr_t) { reset(); return *this; } ScopedPtr(const ScopedPtr&) = delete; ScopedPtr& operator=(const ScopedPtr&) = delete; ScopedPtr(ScopedPtr&& rhs) noexcept : ptr{nullptr} { rhs.swap(*this); } ScopedPtr& operator=(ScopedPtr&& rhs) noexcept { rhs.swap(*this); return *this; } template ScopedPtr(ScopedPtr&& rhs) noexcept : ptr{nullptr} { ScopedPtr tmp{rhs.release(), static_cast(rhs)}; tmp.swap(*this); } template ScopedPtr& operator=(ScopedPtr&& rhs) noexcept { ScopedPtr tmp{rhs.release(), static_cast(rhs)}; tmp.swap(*this); return *this; } template::is_array> typename std::enable_if::type operator[](std::size_t index) const noexcept { return ptr[index]; } template::is_array> typename std::enable_if::type operator*() const noexcept { return *ptr; } pointer operator->() const noexcept { return ptr; } operator bool() const noexcept { return ptr != nullptr; } pointer get() const noexcept { return ptr; } pointer release() noexcept { pointer result = nullptr; std::swap(result, ptr); return result; } void reset(pointer rhs = pointer()) noexcept { pointer old = release(); ptr = rhs; if (old) { destroyer_type::operator()(old); } } void swap(ScopedPtr& rhs) noexcept { std::swap(static_cast(*this), static_cast(rhs)); std::swap(ptr, rhs.ptr); } private: pointer ptr; }; /** @brief Syntactic sugar for creating instances wrapped in a ScopedPtr. @note The default behavior is to rely on the New / Delete pair of lifetime managers, because it's sensible to do so. However, because a significant portion of our abstractions follow the convention of exposing a create / destroy pair of factory functions (where create always returns a raw pointer), there also exists a dedicated FactoryCreate / FactoryDestroy pair of lifetime managers. To change the default behavior in order to utilize a specific lifetime manager pair, specialize the DefaultInstanceCreator and DefaultInstanceDestroyer traits for the types that need different handling. Alternately, it's also possible to pass a custom creator / destroyer on each invocation. */ template()...)) > ::type> ScopedPtr makeScoped(Args&& ... args) { static_assert(std::is_same::value || std::is_base_of::value || std::is_convertible::type>::value, "Incompatible types."); return ScopedPtr{TCreator{} (std::forward(args)...)}; } template class TCreatorTemplate, template class TDestroyerTemplate, typename ... Args> ScopedPtr > makeScoped(Args&& ... args) { using TCreator = TCreatorTemplate; using TDestroyer = TDestroyerTemplate; return makeScoped(std::forward(args)...); } template ScopedPtr::type> makeScoped(Args&& ... args) { using TCreator = typename DefaultInstanceCreator::type; using TDestroyer = typename DefaultInstanceDestroyer::type; return makeScoped(std::forward(args)...); } } // namespace pma