On Thu, 22 May 2025 at 10:38, Tomasz Kamiński <[email protected]> wrote:
>
> From: Jonathan Wakely <[email protected]>
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/allocated_ptr.h (_Scoped_allocation): New class
> template.
>
> Co-Authored-By: Tomasz Kamiński <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> Tested on x86_64-linux. OK for trunk?
OK, thanks.
>
> libstdc++-v3/include/bits/allocated_ptr.h | 96 +++++++++++++++++++++++
> 1 file changed, 96 insertions(+)
>
> diff --git a/libstdc++-v3/include/bits/allocated_ptr.h
> b/libstdc++-v3/include/bits/allocated_ptr.h
> index 0b2b6fe5820..aa5355f0e2f 100644
> --- a/libstdc++-v3/include/bits/allocated_ptr.h
> +++ b/libstdc++-v3/include/bits/allocated_ptr.h
> @@ -36,6 +36,7 @@
> # include <type_traits>
> # include <bits/ptr_traits.h>
> # include <bits/alloc_traits.h>
> +# include <bits/utility.h>
>
> namespace std _GLIBCXX_VISIBILITY(default)
> {
> @@ -136,6 +137,101 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> return { std::__allocate_guarded(__a) };
> }
>
> + // An RAII type that acquires memory from an allocator.
> + // N.B. 'scoped' here in in the RAII sense, not the scoped allocator
> model,
> + // so this has nothing to do with `std::scoped_allocator_adaptor`.
> + // This class can be used to simplify the common pattern:
> + //
> + // auto ptr = alloc.allocate(1);
> + // try {
> + // std::construct_at(std::to_address(ptr), args);
> + // m_ptr = ptr;
> + // } catch (...) {
> + // alloc.deallocate(ptr, 1);
> + // throw;
> + // }
> + //
> + // Instead you can do:
> + //
> + // _Scoped_allocation sa(alloc);
> + // m_ptr = std::construct_at(sa.get(), args);
> + // (void) sa.release();
> + //
> + // Or even simpler:
> + //
> + // _Scoped_allocation sa(alloc, std::in_place, args);
> + // m_ptr = sa.release();
> + //
> + template<typename _Alloc>
> + struct _Scoped_allocation
> + {
> + using value_type = typename allocator_traits<_Alloc>::value_type;
> + using pointer = typename allocator_traits<_Alloc>::pointer;
> +
> + // Use `a` to allocate memory for `n` objects.
> + constexpr explicit
> + _Scoped_allocation(const _Alloc& __a, size_t __n = 1)
> + : _M_a(__a), _M_n(__n), _M_p(_M_a.allocate(__n))
> + { }
> +
> +#if __glibcxx_optional >= 201606L
> + // Allocate memory for a single object and if that succeeds,
> + // construct an object using args.
> + //
> + // Does not do uses-allocator construction; don't use if you need that.
> + //
> + // CAUTION: the destructor will *not* destroy this object, it will only
> + // free the memory. That means the following pattern is unsafe:
> + //
> + // _Scoped_allocation sa(alloc, in_place, args);
> + // potentially_throwing_operations();
> + // return sa.release();
> + //
> + // If the middle operation throws, the object will not be destroyed.
> + template<typename... _Args>
> + constexpr explicit
> + _Scoped_allocation(const _Alloc& __a, in_place_t, _Args&&... __args)
> + : _Scoped_allocation(__a, 1)
> + {
> + // The target constructor has completed, so if the next line throws,
> + // the destructor will deallocate the memory.
> + allocator_traits<_Alloc>::construct(_M_a, get(),
> + std::forward<_Args>(__args)...);
> + }
> +#endif
> +
> + _GLIBCXX20_CONSTEXPR
> + ~_Scoped_allocation()
> + {
> + if (_M_p) [[__unlikely__]]
> + _M_a.deallocate(_M_p, _M_n);
> + }
> +
> + _Scoped_allocation(_Scoped_allocation&&) = delete;
> +
> + constexpr _Alloc
> + get_allocator() const noexcept { return _M_a; }
> +
> + constexpr value_type*
> + get() const noexcept
> + { return std::__to_address(_M_p); }
> +
> + [[__nodiscard__]]
> + constexpr pointer
> + release() noexcept { return std::__exchange(_M_p, nullptr); }
> +
> + private:
> + [[__no_unique_address__]] _Alloc _M_a;
> + size_t _M_n;
> + pointer _M_p;
> + };
> +
> +#if __glibcxx_optional >= 201606L && __cpp_deduction_guides >= 201606L
> + template<typename _Alloc, typename... _Args>
> + _Scoped_allocation(_Alloc, in_place_t, _Args...)
> + -> _Scoped_allocation<_Alloc>;
> +#endif
> +
> /// @endcond
> _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace std
> --
> 2.49.0
>