On Thu, May 22, 2025 at 12:21 PM Daniel Krügler <daniel.krueg...@gmail.com>
wrote:

>
>
> Am Do., 22. Mai 2025 um 11:41 Uhr schrieb Tomasz Kamiński <
> tkami...@redhat.com>:
>
>> 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);
>>
>
> Why is the situation *unlikely* that _M_p has a non-nullptr content?
> Shouldn't that actually be likely?
>
This object manages allocation of raw memory, and not contained objects. So
the non-exceptional code path
looks like:
  _Scoped_allocation a(alloc, in_place, argos);
  _M_ptr = a.release();
And release set the _M_p to nullptr.

In other words, this if is taken only in case of exception.

>
> - Daniel
>
>
>> +      }
>> +
>> +      _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
>>
>>

Reply via email to