// Copyright © Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: Apache-2.0 /*! \file unique_ptr.h * \brief A smart pointer that owns and manages another object through a * pointer and disposes of that object when the \p unique_ptr goes * out of scope. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include THRUST_NAMESPACE_BEGIN /*! \addtogroup memory_management Memory Management * \{ */ /*! \brief Default deleter for \p unique_ptr. * * The default deleter used by \p unique_ptr when no custom deleter is specified. * Calls the destructor (if needed) and deallocates memory using \p thrust::device_free. * * \tparam T The type of object to delete (single object form). * * \see thrust::unique_ptr * \see thrust::device_free */ template struct default_delete; /*! \brief Default deleter specialization for single objects. * * This specialization handles deletion of single objects allocated in device memory. * * \tparam T The type of object to delete. */ template struct default_delete>> { /*! \typedef pointer * \brief The pointer type (`thrust::device_ptr`). */ using pointer = thrust::device_ptr; THRUST_HOST constexpr default_delete() noexcept = default; /*! \brief Converting constructor from compatible deleter. * * Allows construction from a deleter for a convertible type. * * \tparam U A type convertible to \p T. */ template THRUST_HOST default_delete( const default_delete&, std::enable_if_t, pointer>>* = nullptr) noexcept {} /*! \brief Deletes the object pointed to by \p ptr. * * Calls the destructor (if needed) and deallocates memory using thrust::device_free. * * \param ptr Pointer to the object to delete. */ THRUST_HOST void operator()(pointer ptr) const noexcept { if (ptr.get() == nullptr) { return; } // The ideal implementation would be a simple call to `thrust::device_delete`: // // thrust::device_delete(ptr); // // However, for non-trivially destructible types, `thrust::device_delete` // calls `thrust::destroy_range`, which requires an allocator with a // `value_type` typedef. The internal `thrust::detail::device_delete_allocator` // is an empty struct that lacks this typedef, causing a compilation error. // // As a workaround, we manually invoke the destructor on the device using // `thrust::for_each_n` and then separately free the memory. if constexpr (!std::is_trivially_destructible_v) { thrust::for_each_n(ptr, 1, [] __device__(T & x) { x.~T(); }); } thrust::device_free(ptr); } }; /*! \brief Default deleter specialization for arrays of non-trivially destructible types. * * This specialization handles deletion of arrays where element destructors must be called. * * \tparam T The array element type (non-trivially destructible). */ template struct default_delete>> { /*! \typedef pointer * \brief The pointer type (`thrust::device_ptr`). */ using pointer = thrust::device_ptr; // Allow other instantiations to access private members for converting constructor template friend struct default_delete; /*! \brief Constructs a deleter with the specified array size. * * \param n The number of elements in the array (default: 0). */ THRUST_HOST constexpr default_delete(size_t n = 0) noexcept : m_size(n){}; /*! \brief Copy and converting constructor from compatible array deleter. * * Copies the size from another deleter for a convertible array type. * * \tparam U An array element type convertible to \p T. * \param other The deleter to copy from. */ template THRUST_HOST default_delete(const default_delete& other, std::enable_if_t>* = nullptr) noexcept : m_size(other.m_size) {} /*! \brief Deletes the array pointed to by \p ptr. * * Calls destructors (if needed) and deallocates memory using thrust::device_free. * * \param ptr Pointer to the array to delete. */ THRUST_HOST void operator()(pointer ptr) const noexcept { if (ptr.get() == nullptr) { return; } // The ideal implementation would be a call to `thrust::device_delete` // with the number of elements: // // thrust::device_delete(ptr, m_size); // // However, for non-trivially destructible types, `thrust::device_delete` // calls `thrust::destroy_range`, which requires an allocator with a // `value_type` typedef. The internal `thrust::detail::device_delete_allocator` // is an empty struct that lacks this typedef, causing a compilation error. // // As a workaround, we manually invoke the destructor on each element // using `thrust::for_each_n` and then separately free the memory. if (m_size) { thrust::for_each_n(ptr, m_size, [] __device__(T & x) { x.~T(); }); } thrust::device_free(ptr); } private: size_t m_size; }; /*! \brief Default deleter specialization for arrays of trivially destructible types. * * For arrays of trivially destructible element types, this specialization intentionally * does NOT store the element count. Their destruction is a no-op, so only the raw * deallocation is required. Omitting the size keeps this deleter specialization an * empty (zero-size) type, allowing \p unique_ptr instantiations that use it to * remain a low/zero-cost abstraction (the default deleter need not increase the overall * size of unique_ptr above the size of a simple pointer `T *`). * * \tparam T The array element type (trivially destructible). */ template struct default_delete>> { /*! \typedef pointer * \brief The pointer type (`thrust::device_ptr`). */ using pointer = thrust::device_ptr; /*! \brief Constructs a deleter (size parameter ignored for trivially destructible types). * * The size parameter is accepted but ignored since trivially destructible types * don't require element-wise destruction. */ THRUST_HOST constexpr default_delete(size_t = 0) noexcept {}; /*! \brief Copy and converting constructor from compatible deleter. * * Allows construction from a deleter for a convertible type. * * \tparam U A type convertible to \p T. */ template THRUST_HOST default_delete( const default_delete&, std::enable_if_t, pointer>>* = nullptr) noexcept {} /*! \brief Deletes the array pointed to by \p ptr. * * Deallocates memory using thrust::device_free. * * \param ptr Pointer to the array to delete. */ THRUST_HOST void operator()(pointer ptr) const noexcept { thrust::device_free(ptr); } }; namespace detail { template struct pointer_detector { using type = thrust::device_ptr; }; template struct pointer_detector> { using type = typename D::pointer; }; template struct unique_ptr_deleter_sfinae { static_assert(!std::is_reference_v, "incorrect specialization"); using lval_ref_type = const Deleter&; using good_rval_ref_type = Deleter&&; using bad_rval_ref_type = void; using enable_rval_overload = thrust::detail::true_type; }; template struct unique_ptr_deleter_sfinae { using lval_ref_type = const Deleter&; using good_rval_ref_type = void; using bad_rval_ref_type = const Deleter&&; using enable_rval_overload = thrust::detail::false_type; }; template struct unique_ptr_deleter_sfinae { using lval_ref_type = Deleter&; using good_rval_ref_type = void; using bad_rval_ref_type = Deleter&&; using enable_rval_overload = thrust::detail::false_type; }; } // namespace detail /*! \p thrust::unique_ptr is a smart pointer that owns and manages another object, * allocated in device memory, via a pointer and subsequently disposes of that * object when the \p unique_ptr goes out of scope. * * The object is disposed of using the associated `Deleter` when either of the * following happens: * - the managing `unique_ptr` object is destroyed. * - the managing `unique_ptr` object is assigned another pointer via `operator=` or `reset()`. * * The object is disposed of by calling `get_deleter()(get())`. The default deleter, * `thrust::default_delete`, calls the destructor (if needed) and deallocates memory * using `thrust::device_free`. * * A `unique_ptr` may alternatively own no object, in which case it is described as * *empty*. * * There are two versions of `thrust::unique_ptr`: * 1. Manages a single object * 2. Manages a dynamically-allocated array of objects * * \par Nested Types * - `pointer` - The type of the stored pointer (defaults to `thrust::device_ptr`) * - `element_type` - The type of the managed object (`T`) * - `deleter_type` - The type of the deleter (`D`) * * \tparam T The type of the managed object. * \tparam D The type of the deleter. * * \see thrust::default_delete * \see thrust::make_unique * \see thrust::device_ptr * \see thrust::device_malloc * \see thrust::device_new * \see thrust::device_free * \see https://en.cppreference.com/w/cpp/memory/unique_ptr */ template > class __attribute__((trivial_abi)) unique_ptr { public: /*! \typedef pointer * \brief The type of the stored pointer (defaults to `thrust::device_ptr`). */ using pointer = typename thrust::detail::pointer_detector::type; /*! \typedef element_type * \brief The type of the managed object (`T`). */ using element_type = T; /*! \typedef deleter_type * \brief The type of the deleter (`D`). */ using deleter_type = D; // TODO: When a standard "trivially relocatable" facility lands, add an // annotation/macro here to advertise that unique_ptr can be bitwise relocated private: pointer m_ptr; [[no_unique_address]] deleter_type m_deleter; using DeleterSFINAE = thrust::detail::unique_ptr_deleter_sfinae; // Next section implements SFINAE constraints for the unique_ptr constructors // using a pattern that mirrors the implementation in libc++. // // The `dependent_type` helper makes the type aliases below (e.g., LValRefType) // dependent on a dummy template parameter from the constructor itself. This // forces the compiler to defer constraint checking until function overload // resolution, rather than at class instantiation time. // // NOTE: A simpler SFINAE pattern using a `static constexpr bool` evaluated at // class-instantiation time also works correctly. However, we intentionally // follow the more complex libc++ pattern for consistency with a proven // implementation, aiming to inherit its robustness against // potential compiler-specific edge cases. template using LValRefType = typename thrust::detail::dependent_type::type::lval_ref_type; template using GoodRValRefType = typename thrust::detail::dependent_type::type::good_rval_ref_type; template using BadRValRefType = typename thrust::detail::dependent_type::type::bad_rval_ref_type; template < bool Dummy, class Deleter = typename thrust::detail::dependent_type::type, Dummy>::type> using EnableIfDeleterDefaultConstructible = std::enable_if_t && !std::is_pointer_v>; template using EnableIfDeleterConstructible = std::enable_if_t>; template using EnableIfMoveConvertible = std::enable_if_t && !std::is_array_v>; template using EnableIfDeleterConvertible = std::enable_if_t<(std::is_reference_v && std::is_same_v) || (!std::is_reference_v && std::is_convertible_v)>; template using EnableIfDeleterAssignable = std::enable_if_t>; template < bool Dummy, class Deleter = typename thrust::detail::dependent_type::type, Dummy>::type> using EnableIfDeleterDefaultDelete = std::enable_if_t>>; public: //========================================================================== // Constructors //========================================================================== /*! \brief Constructs a \p unique_ptr that does not own an object. */ template > THRUST_HOST constexpr unique_ptr() noexcept : m_ptr() , m_deleter() {} /*! \brief Constructs a \p unique_ptr that does not own an object. */ template > THRUST_HOST constexpr unique_ptr(std::nullptr_t) noexcept : unique_ptr() {} /*! \brief Constructs a \p unique_ptr that owns the object pointed to by \p p. * \param p A pointer to the object in device memory to manage. */ template > THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(pointer p) noexcept : m_ptr(p) , m_deleter() {} /*! \brief Constructs a \p unique_ptr that owns the object pointed to by \p raw_p. * \param raw_p A raw pointer to the object in device memory to manage. */ template , class = EnableIfDeleterDefaultDelete> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(T* raw_p) noexcept : m_ptr(device_pointer_cast(raw_p)) , m_deleter() {} /*! \brief Constructs a \p unique_ptr that owns the object pointed to by \p p and uses \p d as the deleter. * \param p A pointer to the object in device memory to manage. * \param d The deleter to use. */ template >> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(pointer p, LValRefType d) noexcept : m_ptr(p) , m_deleter(d) {} /*! \brief Constructs a \p unique_ptr that owns the object pointed to by \p p and uses \p d as the deleter. * \param p A pointer to the object in device memory to manage. * \param d The deleter to use. */ template >> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(pointer p, GoodRValRefType d) noexcept : m_ptr(p) , m_deleter(std::move(d)) { static_assert(!std::is_reference_v, "rvalue deleter bound to reference"); } template >> unique_ptr(pointer p, BadRValRefType d) = delete; /*! \brief Move constructor. Constructs a \p unique_ptr by taking ownership of the object managed by \p u. * \param u The \p unique_ptr to move from. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& u) noexcept : m_ptr(u.release()) , m_deleter(std::forward(u.get_deleter())) {} /*! \brief Converting move constructor. Constructs a \p unique_ptr by taking ownership of the object managed by \p u. * * Allows converting from \p unique_ptr to \p unique_ptr when * the pointer and deleter types are compatible. * * \param u The \p unique_ptr to move from. */ template , U>, class = EnableIfDeleterConvertible> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& u) noexcept : m_ptr(u.release()) , m_deleter(std::forward(u.get_deleter())) {} //========================================================================== // Assignment //========================================================================== /*! \brief Move assignment operator. Replaces the managed object with the one from \p u. * \param u The \p unique_ptr to move from. * \return `*this` */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& u) noexcept { reset(u.release()); m_deleter = std::forward(u.get_deleter()); return *this; } /*! \brief Converting assignment operator.. Replaces the managed object with the one from \p u. * \param u The \p unique_ptr to move from. * \return `*this` */ template , U>, class = EnableIfDeleterAssignable> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& u) noexcept { reset(u.release()); m_deleter = std::forward(u.get_deleter()); return *this; } /*! \brief Assigns a null pointer, deallocating the managed object. Effectively the same as calling reset(). * \return `*this` */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(std::nullptr_t) noexcept { reset(); return *this; } //========================================================================== // Destructor //========================================================================== /*! \brief Destroys the \p unique_ptr, the managed object is destroyed via `get_deleter()(get())`. If get() == nullptr there are no effects. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 ~unique_ptr() { reset(); } //========================================================================== // Observers //========================================================================== /*! \brief Returns a pointer to the managed object or `nullptr` if no object is owned. * \return Pointer to the managed object. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 pointer get() const noexcept { return m_ptr; } /*! \brief Returns a raw pointer to the managed object or `nullptr` if no object is owned. * \return Raw pointer to the managed object. */ template > THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 T* get_raw() const noexcept { return raw_pointer_cast(m_ptr); } /*! \brief Returns a reference to the deleter object which would be used for destruction of the managed object. * \return A reference to the stored deleter. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 deleter_type& get_deleter() noexcept { return m_deleter; } /*! \brief Returns a reference to the deleter object which would be used for destruction of the managed object. * \return A reference to the stored deleter. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 const deleter_type& get_deleter() const noexcept { return m_deleter; } /*! \brief Checks if the \p unique_ptr owns an object. * * Equivalent to `get() != nullptr`. * * \return `true` if an object is owned, `false` otherwise. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit operator bool() const noexcept { return m_ptr != nullptr; } /*! \brief Dereferences the stored pointer. * * The default `unique_ptr` implementation uses `thrust::device_ptr`. * Dereferencing this pointer in host code is a valid operation that * results in a copy of the object from device to host memory. * * \return A reference to the managed object. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 auto operator*() const noexcept { return *m_ptr; } /*! \brief Disabled for device pointers - cannot dereference device memory from host. * * \warning This operator is intentionally disabled because dereferencing * device memory from host code is invalid. Use `operator*()` to copy the * object to host first (`T host_obj = *ptr`), or access members inside * device code. * * Attempting to use this operator will produce a compile-time error with * a clear diagnostic message. * * \return This function does not return; it triggers a compile-time error. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 pointer operator->() const noexcept { static_assert(false, "thrust::unique_ptr::operator->(): cannot dereference device memory from host. " "Copy the object to host first (T host = *ptr) or access members inside device code."); return m_ptr; } //========================================================================== // Modifiers //========================================================================== /*! \brief Releases ownership of the managed object, if any. * \return A pointer to the released object. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 pointer release() noexcept { pointer temp = m_ptr; m_ptr = pointer(); return temp; } /*! \brief Replaces the managed object. * \param p The new object to manage. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 void reset(pointer p = pointer()) noexcept { pointer temp = m_ptr; m_ptr = p; if (temp) { m_deleter(temp); } } /*! \brief Swaps the managed object and deleter with another \p unique_ptr. * * Exchanges the contents of `*this` and \p u. This operation is `noexcept` * and swaps both the stored pointer and the deleter. * * \param u The \p unique_ptr to swap with. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& u) noexcept { using std::swap; swap(m_ptr, u.m_ptr); swap(m_deleter, u.m_deleter); } }; /*! \brief Array specialization of \p unique_ptr for managing dynamically-allocated arrays. * * This specialization manages arrays allocated in device memory. The array form * provides `operator[]` for element access. * * \par Array Destruction Behavior * The array form has different constructor requirements and performance characteristics * based on the element type's destructibility: * * **Trivially-destructible types** (e.g., `int`, `float`, POD types): * - Constructors do NOT require array size parameter * - Deleter is zero-size (no memory overhead) * - Destruction only deallocates memory (no destructors to call) * - Example: `unique_ptr ptr(device_malloc(100));` * * **Non-trivially-destructible types** (e.g., types with custom destructors): * - Constructors REQUIRE array size parameter * - Deleter stores the size (small memory overhead: one `size_t`) * - Destruction calls destructor for each element on device before deallocation * - Example: `unique_ptr ptr(device_new(100), 100);` * * This design ensures zero overhead for simple types while properly handling * complex types that require cleanup. * * \tparam T The array element type. * \tparam D The type of the deleter. * * \see thrust::default_delete * \see thrust::make_unique * \see https://en.cppreference.com/w/cpp/memory/unique_ptr */ template class __attribute__((trivial_abi)) unique_ptr { public: /*! \typedef pointer * \brief The type of the stored pointer (defaults to `thrust::device_ptr`). */ using pointer = typename thrust::detail::pointer_detector::type; /*! \typedef element_type * \brief The type of the array elements (`T`). */ using element_type = T; /*! \typedef deleter_type * \brief The type of the deleter (`D`). */ using deleter_type = D; private: template friend class unique_ptr; pointer m_ptr; [[no_unique_address]] deleter_type m_deleter; using DeleterSFINAE = thrust::detail::unique_ptr_deleter_sfinae; template using LValRefType = typename thrust::detail::dependent_type::type::lval_ref_type; template using GoodRValRefType = typename thrust::detail::dependent_type::type::good_rval_ref_type; template using BadRValRefType = typename thrust::detail::dependent_type::type::bad_rval_ref_type; template < bool Dummy, class Deleter = typename thrust::detail::dependent_type::type, Dummy>::type> using EnableIfDeleterDefaultConstructible = std::enable_if_t && !std::is_pointer_v>; template using EnableIfDeleterConstructible = std::enable_if_t>; template using EnableIfPointerConvertible = std::enable_if_t>; template ::type, Dummy>::type> using EnableIfTriviallyDestructible = std::enable_if_t>; template ::type, Dummy>::type> using EnableIfNotTriviallyDestructible = std::enable_if_t>; template using EnableIfMoveConvertible = std::enable_if_t && std::is_same_v && std::is_same_v && std::is_convertible_v>; template using EnableIfDeleterConvertible = std::enable_if_t<(std::is_reference_v && std::is_same_v) || (!std::is_reference_v && std::is_convertible_v)>; template using EnableIfDeleterAssignable = std::enable_if_t>; template < bool Dummy, class Deleter = typename thrust::detail::dependent_type::type, Dummy>::type> using EnableIfDeleterDefaultDelete = std::enable_if_t>>; public: //========================================================================== // Constructors //========================================================================== /*! \brief Constructs an empty \p unique_ptr that does not own an array. */ template > THRUST_HOST constexpr unique_ptr() noexcept : m_ptr() , m_deleter() {} /*! \brief Constructs an empty \p unique_ptr that does not own an array. */ template > THRUST_HOST constexpr unique_ptr(std::nullptr_t) noexcept : unique_ptr() {} /*! \brief Constructs a \p unique_ptr that owns the object pointed to by \p p. * * This overload is only available for arrays of trivially-destructible types. * * \param p A pointer to an array in device memory to manage. */ template , class = EnableIfPointerConvertible, class = EnableIfTriviallyDestructible> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(Pp p) noexcept : m_ptr(p) , m_deleter() {} /*! \brief Constructs a \p unique_ptr from a raw device array pointer. * * This overload is only available for arrays of trivially-destructible types. * * \param raw_p A raw pointer to an array in device memory to manage. */ template , class = EnableIfPointerConvertible>, class = EnableIfTriviallyDestructible, class = EnableIfDeleterDefaultDelete> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(Pp* raw_p) noexcept : m_ptr(device_pointer_cast(raw_p)) , m_deleter() {} /*! \brief Constructs a \p unique_ptr that owns the object pointed to by \p p with known size. * * For arrays of non-trivially-destructible types, the size is required to ensure * all element destructors are properly called during deletion. * * \param p A pointer to an array in device memory to manage. * \param size The number of elements in the array. */ template , class = EnableIfPointerConvertible> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(Pp p, size_t size) noexcept : m_ptr(p) , m_deleter(size) {} /*! \brief Constructs a \p unique_ptr from a raw device array pointer with known size. * * For arrays of non-trivially-destructible types, the size is required to ensure * all element destructors are properly called during deletion. * * \param raw_p A raw pointer to an array in device memory to manage. * \param size The number of elements in the array. */ template , class = EnableIfPointerConvertible>, class = EnableIfDeleterDefaultDelete> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(Pp* raw_p, size_t size) noexcept : m_ptr(device_pointer_cast(raw_p)) , m_deleter(size) {} /*! \brief Constructs a \p unique_ptr with a custom deleter (lvalue reference). * * \param p A pointer to the array in device memory to manage. * \param deleter The deleter to use for destroying the array. */ template >, class = EnableIfPointerConvertible> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(Pp p, LValRefType deleter) noexcept : m_ptr(p) , m_deleter(deleter) {} /*! \brief Constructs an empty \p unique_ptr with a custom deleter (lvalue reference). * * \param deleter The deleter to store. */ template >> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(std::nullptr_t, LValRefType deleter) noexcept : m_ptr(nullptr) , m_deleter(deleter) {} /*! \brief Constructs a \p unique_ptr with a custom deleter (rvalue reference). * * \param p A pointer to the array in device memory to manage. * \param deleter The deleter to use for destroying the array (moved). */ template >, class = EnableIfPointerConvertible> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(Pp p, GoodRValRefType deleter) noexcept : m_ptr(p) , m_deleter(std::move(deleter)) { static_assert(!std::is_reference_v, "rvalue deleter bound to reference"); } /*! \brief Constructs an empty \p unique_ptr with a custom deleter (rvalue reference). * * \param deleter The deleter to store (moved). */ template >> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(std::nullptr_t, GoodRValRefType deleter) noexcept : m_ptr(nullptr) , m_deleter(std::move(deleter)) { static_assert(!std::is_reference_v, "rvalue deleter bound to reference"); } template >, class = EnableIfPointerConvertible> THRUST_HOST unique_ptr(Pp ptr, BadRValRefType deleter) = delete; /*! \brief Move constructor that transfers ownership from another array \p unique_ptr. * * \param u The \p unique_ptr to move from. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& u) noexcept : m_ptr(u.release()) , m_deleter(std::forward(u.get_deleter())) {} /*! \brief Converting move constructor from a compatible array \p unique_ptr. * * Allows converting from \p unique_ptr to \p unique_ptr when * the array element types and deleter types are compatible (e.g., derived to base). * * \tparam Up An array element type convertible to \p T. * \tparam Ep A deleter type convertible to \p D. * \param u The \p unique_ptr to move from. */ template , Up>, class = EnableIfDeleterConvertible> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& u) noexcept : m_ptr(u.release()) , m_deleter(std::forward(u.get_deleter())) {} //========================================================================== // Assignment //========================================================================== /*! \brief Move assignment operator. Replaces the managed object with the one from \p u. * * Releases the currently managed array (if any) and takes ownership of * the array managed by \p p. * * \param p The \p unique_ptr to move from. * \return `*this` */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& p) noexcept { reset(p.release()); m_deleter = std::forward(p.get_deleter()); return *this; } /*! \brief Converting assignment operator. Replaces the managed object with the one from \p p. * \param p The \p unique_ptr to move from. * \return `*this` */ template , Up>, class = EnableIfDeleterAssignable> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& p) noexcept { reset(p.release()); m_deleter = std::forward(p.get_deleter()); return *this; } /*! \brief Assigns a null pointer, deallocating the managed object. Effectively the same as calling reset(). * \return `*this` */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(std::nullptr_t) noexcept { reset(); return *this; } //========================================================================== // Destructor //========================================================================== /*! \brief Destroys the \p unique_ptr, the managed array is destroyed via `get_deleter()(get())`. If get() == nullptr there are no effects. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 ~unique_ptr() { reset(); } //========================================================================== // Observers //========================================================================== /*! \brief Accesses an element of the managed array. * * For device arrays, accessing an element from host code triggers a copy * from device to host memory. * * \warning Using this operator when iterating over the array is STRONGLY discouraged * as the excessive number of single-element copies usually results in poor performance. * * \param i The index of the element to access. * \return A reference to (or copy of) the element at index \p i. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 auto operator[](size_t i) const noexcept { return m_ptr[i]; } /*! \brief Returns a pointer to the managed array or `nullptr` if no object is owned. * * \return Pointer to the managed array. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 pointer get() const noexcept { return m_ptr; } /*! \brief Returns a raw pointer to the managed array or `nullptr` if no object is owned. * * \return Pointer to the managed array. */ template > THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 T* get_raw() const noexcept { return raw_pointer_cast(m_ptr); } /*! \brief Returns a reference to the deleter object which would be used for destruction of the managed array. * \return A reference to the stored deleter. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 deleter_type& get_deleter() noexcept { return m_deleter; } /*! \brief Returns a const reference to the deleter object which would be used for destruction of the managed array. * \return A const reference to the stored deleter. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 const deleter_type& get_deleter() const noexcept { return m_deleter; } /*! \brief Checks if the \p unique_ptr owns an array. * \return `true` if an array is owned, `false` otherwise. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 explicit operator bool() const noexcept { return m_ptr != nullptr; } //========================================================================== // Modifiers //========================================================================== /*! \brief Releases ownership of the managed array, if any. * \return A pointer to the released array. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 pointer release() noexcept { pointer temp = m_ptr; m_ptr = pointer(); return temp; } /*! \brief Replaces the managed array. * \param p The new array to manage. */ template > THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 void reset(Pp p) noexcept { pointer temp = m_ptr; m_ptr = p; if (temp) { m_deleter(temp); } } /*! \brief Replaces the managed array with \p nullptr. * * Destroys the currently managed array (if any) and resets to empty state. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 void reset(std::nullptr_t = nullptr) noexcept { pointer temp = m_ptr; m_ptr = nullptr; if (temp) { m_deleter(temp); } } /*! \brief Swaps the managed array and deleter with another array \p unique_ptr. * \param u The \p unique_ptr to swap with. */ THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& u) noexcept { using std::swap; swap(m_ptr, u.m_ptr); swap(m_deleter, u.m_deleter); } }; /*! \brief Swaps two \p unique_ptr objects. * * Exchanges the contents of \p x and \p y by calling `x.swap(y)`. * * \param x The first \p unique_ptr. * \param y The second \p unique_ptr. */ template , void>> inline THRUST_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& x, unique_ptr& y) noexcept { x.swap(y); } //============================================================================== // Comparison Operators //============================================================================== /*! \brief Compares two \p unique_ptr objects for equality. * * Two \p unique_ptr objects are considered equal if they point to the same * memory address or are both null. * * \param x The first \p unique_ptr to compare. * \param y The second \p unique_ptr to compare. * \return `true` if the pointers are equal, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator==(const unique_ptr& x, const unique_ptr& y) { return x.get() == y.get(); } #if THRUST_STD_VER <= 17 template /*! \brief Compares two \p unique_ptr objects for inequality (C++17 and earlier). * * \param x The first \p unique_ptr to compare. * \param y The second \p unique_ptr to compare. * \return `true` if the pointers are not equal, `false` otherwise. */ THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator!=(const unique_ptr& x, const unique_ptr& y) { return !(x == y); } #endif /*! \brief Compares two \p unique_ptr objects using less-than ordering. * * \param x The first \p unique_ptr to compare. * \param y The second \p unique_ptr to compare. * \return `true` if the pointer stored in \p x is less than the pointer stored in \p y, `false` otherwise. * * \note Operators `>`, `<=`, and `>=` are also provided and defined in terms of this operator. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator<(const unique_ptr& x, const unique_ptr& y) { using P1 = typename unique_ptr::element_type*; using P2 = typename unique_ptr::element_type*; using CTP = typename std::common_type::type; return std::less()(thrust::raw_pointer_cast(x.get()), thrust::raw_pointer_cast(y.get())); } /*! \brief Compares two \p unique_ptr objects using greater-than ordering. * * \param x The first \p unique_ptr to compare. * \param y The second \p unique_ptr to compare. * \return `true` if \p x is greater than \p y, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator>(const unique_ptr& x, const unique_ptr& y) { return y < x; } /*! \brief Compares two \p unique_ptr objects using less-than-or-equal ordering. * * \param x The first \p unique_ptr to compare. * \param y The second \p unique_ptr to compare. * \return `true` if \p x is less than or equal to \p y, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator<=(const unique_ptr& x, const unique_ptr& y) { return !(y < x); } /*! \brief Compares two \p unique_ptr objects using greater-than-or-equal ordering. * * \param x The first \p unique_ptr to compare. * \param y The second \p unique_ptr to compare. * \return `true` if \p x is greater than or equal to \p y, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator>=(const unique_ptr& x, const unique_ptr& y) { return !(x < y); } #if THRUST_STD_VER >= 20 template THRUST_HOST inline auto operator<=> (const unique_ptr& x, const unique_ptr& y) { // TODO: once thrust::device_ptr supports three_way_comparison, we should be using that return std::compare_three_way()(x.get_raw(), y.get_raw()); } #endif /*! \brief Compares a \p unique_ptr with nullptr for equality. * * \param x The \p unique_ptr to compare. * \return `true` if \p x is empty, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator==(const unique_ptr& x, std::nullptr_t) noexcept { return !x; } #if THRUST_STD_VER <= 17 /*! \brief Compares nullptr with a \p unique_ptr for equality (C++17 and earlier). * * \param y The \p unique_ptr to compare. * \return `true` if \p y is empty, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator==(std::nullptr_t, const unique_ptr& y) noexcept { return !y; } /*! \brief Compares a \p unique_ptr with nullptr for inequality (C++17 and earlier). * * \param x The \p unique_ptr to compare. * \return `true` if \p x is not empty, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator!=(const unique_ptr& x, std::nullptr_t) noexcept { return static_cast(x); } /*! \brief Compares nullptr with a \p unique_ptr for inequality (C++17 and earlier). * * \param y The \p unique_ptr to compare. * \return `true` if \p y is not empty, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator!=(std::nullptr_t, const unique_ptr& y) noexcept { return static_cast(y); } #endif /*! \brief Compares a \p unique_ptr with nullptr using less-than ordering. * * \param x The \p unique_ptr to compare. * \return `true` if the pointer in \p x is less than nullptr, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator<(const unique_ptr& x, std::nullptr_t) { return std::less::pointer>()(x.get(), nullptr); // x.get() < nullptr; } /*! \brief Compares nullptr with a \p unique_ptr using less-than ordering. * * \param y The \p unique_ptr to compare. * \return `true` if nullptr is less than the pointer in \p y, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator<(std::nullptr_t, const unique_ptr& y) { return std::less::pointer>()(nullptr, y.get()); // nullptr < y.get(); } /*! \brief Compares a \p unique_ptr with nullptr using greater-than ordering. * * \param x The \p unique_ptr to compare. * \return `true` if the pointer in \p x is greater than nullptr, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator>(const unique_ptr& x, std::nullptr_t) { return nullptr < x; } /*! \brief Compares nullptr with a \p unique_ptr using greater-than ordering. * * \param y The \p unique_ptr to compare. * \return `true` if nullptr is greater than the pointer in \p y, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator>(std::nullptr_t, const unique_ptr& y) { return y < nullptr; } /*! \brief Compares a \p unique_ptr with nullptr using less-than-or-equal ordering. * * \param x The \p unique_ptr to compare. * \return `true` if the pointer in \p x is less than or equal to nullptr, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator<=(const unique_ptr& x, std::nullptr_t) { return !(nullptr < x); } /*! \brief Compares nullptr with a \p unique_ptr using less-than-or-equal ordering. * * \param y The \p unique_ptr to compare. * \return `true` if nullptr is less than or equal to the pointer in \p y, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator<=(std::nullptr_t, const unique_ptr& y) { return !(y < nullptr); } /*! \brief Compares a \p unique_ptr with nullptr using greater-than-or-equal ordering. * * \param x The \p unique_ptr to compare. * \return `true` if the pointer in \p x is greater than or equal to nullptr, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator>=(const unique_ptr& x, std::nullptr_t) { return !(x < nullptr); } /*! \brief Compares nullptr with a \p unique_ptr using greater-than-or-equal ordering. * * \param y The \p unique_ptr to compare. * \return `true` if nullptr is greater than or equal to the pointer in \p y, `false` otherwise. */ template THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 bool operator>=(std::nullptr_t, const unique_ptr& y) { return !(y < nullptr); } #if THRUST_STD_VER >= 20 template THRUST_HOST inline auto operator<=> (const unique_ptr& x, std::nullptr_t) { // TODO: once thrust::device_ptr supports three_way_comparison, we should be using that return std::compare_three_way()(x.get_raw(), static_cast(nullptr)); } #endif //============================================================================== // Make unique //============================================================================== /*! \brief Constructs an object of type \p T in device memory and wraps it in a \p unique_ptr. * * Allocates device memory for a single object of type \p T, direct-initializes the object * by forwarding the provided arguments, and returns a \p unique_ptr managing the * allocated object. * * This overload participates in overload resolution only if \p T is not an array type. * * \tparam T The type of object to construct (must not be an array). * \tparam Args The types of arguments to forward to the constructor of \p T. * \param args Arguments to forward to the constructor of \p T. * \return A \p unique_ptr managing the newly created object. */ template >> THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr make_unique(Args&&... args) { thrust::device_ptr p = thrust::device_malloc(1); return unique_ptr(thrust::device_new(p, T(std::forward(args)...), 1)); } /*! \brief Constructs an array of objects of type \p T in device memory and wraps it in a \p unique_ptr. * * Allocates device memory for an array of \p n objects of type \p U (where \p T is \p U[]), * default-initializes each element, and returns a \p unique_ptr managing the allocated array. * * This overload participates in overload resolution only if \p T is an array of unknown * bound (e.g., \p T[]). * * \tparam T The array type (e.g., \p int[], \p MyClass[]). * \param n The number of elements in the array. * \return A \p unique_ptr managing the newly created array. */ template ::value>> THRUST_HOST inline THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr make_unique(size_t n) { using U = typename std::remove_extent::type; return unique_ptr(thrust::device_new(n), n); } template ::value>> THRUST_HOST void make_unique(Args&&...) = delete; #if THRUST_STD_VER >= 20 /*! \brief Constructs an object of type \p T in device memory without initialization (C++20). * * Allocates device memory for a single object of type \p T without initializing it, * and returns a \p unique_ptr managing the allocated memory. The object has * indeterminate value. * * This overload participates in overload resolution only if \p T is not an array type. * * \tparam T The type of object to allocate (must not be an array). * \return A \p unique_ptr managing the uninitialized memory. */ template >> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr make_unique_for_overwrite() { return unique_ptr(thrust::device_malloc(1)); } /*! \brief Constructs an array without initialization (C++20). * * Allocates device memory for an array of \p n objects of type \p U (where \p T is \p U[]) * without initializing the elements, and returns a \p unique_ptr managing the allocated * array. The elements have indeterminate values. * * This overload participates in overload resolution only if \p T is an array of unknown * bound (e.g., \p T[]). * * \tparam T The array type (e.g., \p int[], \p MyClass[]). * \param n The number of elements in the array. * \return A \p unique_ptr managing the uninitialized array. */ template ::value>> THRUST_HOST THRUST_CONSTEXPR_SINCE_CXX23 unique_ptr make_unique_for_overwrite(size_t n) { using U = typename std::remove_extent::type; return unique_ptr(thrust::device_malloc(n), n); } template ::value>> THRUST_HOST void make_unique_for_overwrite(Args&&...) = delete; #endif /*! \} // end smart_pointers */ THRUST_NAMESPACE_END