From: Jonathan Wakely <jwak...@redhat.com> libstdc++-v3/ChangeLog:
* include/bits/allocated_ptr.h (_Scoped_allocation): New class template. Co-Authored-By: Tomasz Kamiński <tkami...@redhat.com> Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> --- Tested on x86_64-linux. OK for trunk? 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