polymorphic_allocator::destroy
For C++26| Document #: | P2875R1 |
| Date: | 2023-08-15 |
| Project: | Programming Language C++ |
| Audience: |
Library Evolution Incubator |
| Reply-to: |
Alisdair Meredith <ameredith1@bloomberg.net> |
The member function
polymorphic_allocator::destroy
was deprecated by C++23 as it defines the same semantics that would be
synthesized automatically by
std::allocator_traits. However,
some common use cases for
std::pmr::polymorphic_allocator
do not involve generic code and thus do not necessarily use
std::allocator_traits to call on
the services of such allocators. This paper recommends undeprecating
that function and restoring its wording to the main Standard clause.
allocator_traits in
delete_objectInitial draft of this paper.
At the start of the C++23 cycle, [P2139R2] tried to review each deprecated feature of C++, to see which we would benefit from actively removing, and which might now be better undeprecated. Consolidating all this analysis into one place was intended to ease the (L)EWG review process, but in return gave the author so much feedback that the next revision of that paper was not completed.
For the C++26 cycle there will be a concise paper tracking the overall review process, [P2863R0], but all changes to the standard will be pursued through specific papers, decoupling progress from the larger paper so that delays on a single feature do not hold up progress on all.
This paper takes up the deprecated member function std::polymorphic_allocator::destroy,
D.18
[depr.mem.poly.allocator.mem].
This feature was deprecated by [LWG3036].
Are we in favor of deprecation, pending on paper [P0339R6]
F
|
N
|
A
|
|---|---|---|
| 5 | 3 | 2 |
Moved to Tentatively Ready after seven votes in favour.
Adopted for C++23 by omnibus issues paper [P2236R0].
std::pmr::polymorphic_allocator
is an allocator that will often be used in non-generic circumstances,
unlike std::allocator. This
member function that could otherwise be synthesized by
std::allocator_traits should
still be part of its pubic interface for direct use.
Hence, this paper recommends undeprecating the
destroy member function, as the
natural and expected analog paired with
construct.
polymorphic_allocator has
construct, so logically it
should also have destroy. If I
see a class that overrides new
but does not override delete, I
get suspicious, at best, and disgusted at worst. If I write code that
uses construct, I will probably
also want to call destroy, even
if I know that it is a no-op or can be expressed another way.
Code that does not use an allocator template (e.g.,
experimental::function from the
LFTS), can use
polymorphic_allocator to avoid
type erasure. Such code would not need to use the
allocator_traits indirection and
would call allocate,
construct,
destroy, and
deallocate directly. Yes, it
could use destroy_at directly,
but that breaks abstraction and symmetry (see
above). Any such existing code would need to change if
destroy is removed.
[[deprecated]]If one of the goals is to not write something that
allocator_traits already does
for you, then allocator_traits
needs to detect the presence or absence of member-function
destroy. That detection will
invariably cause a deprecation warning if
destroy is annotated as
[[deprecated]]. Therefore, when
the destroy method is eventually
remove, unsuspecting code breakage will occur.
NOTE: it has since been reported that the deprecation warning can be
turned off in a platform-specific way using pragmas within
allocator_traits. Alternatively,
allocator_traits can be
specialized for
polymorphic_allocator to avoid
calling the deprecated member function.
The deprecation and removal of
destroy has very little benefit
to the standard — certainly not enough to justify breaking code (see usage above).
All changes are relative to [N4950].
20.4.3.1 [mem.poly.allocator.class.general] General
2
A specialization of class template
pmr::polymorphic_allocator meets
the allocator completeness requirements (16.4.4.6.2
[allocator.requirements.completeness])
if its template argument is a cv-unqualified object type.
namespace std::pmr {
template<class Tp = byte> class polymorphic_allocator {
memory_resource* memory_rsrc; // exposition only
public:
using value_type = Tp;
// 20.4.3.2
[mem.poly.allocator.ctor], constructors
polymorphic_allocator() noexcept;
polymorphic_allocator(memory_resource* r);
polymorphic_allocator(const polymorphic_allocator& other) = default;
template<class U>
polymorphic_allocator(const polymorphic_allocator<U>& other) noexcept;
polymorphic_allocator& operator=(const polymorphic_allocator&) = delete;
// 20.4.3.3
[mem.poly.allocator.mem], member functions
[[nodiscard]] Tp* allocate(size_t n);
void deallocate(Tp* p, size_t n);
[[nodiscard]] void* allocate_bytes(size_t nbytes, size_t alignment = alignof(max_align_t));
void deallocate_bytes(void* p, size_t nbytes, size_t alignment = alignof(max_align_t));
template<class T> [[nodiscard]] T* allocate_object(size_t n = 1);
template<class T> void deallocate_object(T* p, size_t n = 1);
template<class T, class... CtorArgs> [[nodiscard]] T* new_object(CtorArgs&&... ctor_args);
template<class T> void delete_object(T* p);
template<class T, class... Args>
void construct(T* p, Args&&... args);
template<class T>
void destroy(T* p);
polymorphic_allocator select_on_container_copy_construction() const;
memory_resource* resource() const;
// friends
friend bool operator==(const polymorphic_allocator& a,
const polymorphic_allocator& b) noexcept {
return *a.resource() == *b.resource();
}
};
}20.4.3.3 [mem.poly.allocator.mem] Member functions
template<class T>
void delete_object(T* p);13 Effects: Equivalent to:
allocator_traits<polymorphic_allocator>::destroy(*this, p);
deallocate_object(p);template<class T, class... Args>
void construct(T* p, Args&&... args);14
Mandates: Uses-allocator construction of
T with allocator
*this (see 20.2.8.2
[allocator.uses.construction])
and constructor arguments std::forward<Args>(args)... is
well-formed.
15
Effects: Construct a T
object in the storage whose address is represented by
p by uses-allocator construction
with allocator *this and
constructor arguments std::forward<Args>(args)....
16
Throws: Nothing unless the constructor for
T throws.
template<class T>
void destroy(T* p);X
Effects: As if by
p->~T().
polymorphic_allocator select_on_container_copy_construction() const;17
Returns:
polymorphic_allocator().
18 [Note 4: The memory resource is not propagated. —end note]
D.18 [depr.mem.poly.allocator.mem] Deprecated polymorphic_allocator member function
1 The following member is declared in addition to those members specified in 20.4.3.3 [mem.poly.allocator.mem]:
namespace std::pmr {
template<class Tp = byte>
class polymorphic_allocator {
public:
template <class T>
void destroy(T* p);
};
}template<class T>
void destroy(T* p);2
Effects: As if by
p->~T().
Thanks to Michael Park for the pandoc-based framework used to transform this document’s source from Markdown.
Thanks to Pablo Halpern for good reviews and helping to organize the rationale.