问题
Say I have a shared_ptr
with a custom allocator and a custom deleter.
I can't find anything in the standard that talks about where the deleter should be stored: it doesn't say that the custom allocator will be used for the deleter's memory, and it doesn't say that it won't be.
Is this unspecified or am I just missing something?
回答1:
util.smartptr.shared.const/9 in C++ 11:
Effects: Constructs a shared_ptr object that owns the object p and the deleter d. The second and fourth constructors shall use a copy of a to allocate memory for internal use.
The second and fourth constructors have these prototypes:
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);
In the latest draft, util.smartptr.shared.const/10 is equivalent for our purpose:
Effects: Constructs a shared_ptr object that owns the object p and the deleter d. When T is not an array type, the first and second constructors enable shared_from_this with p. The second and fourth constructors shall use a copy of a to allocate memory for internal use. If an exception is thrown, d(p) is called.
So the allocator is used if there is a need to allocate it in allocated memory. Based on the current standard and at relevant defect reports, allocation is not mandatory but assumed by the committee.
Although the interface of
shared_ptr
allows an implementation where there is never a control block and allshared_ptr
andweak_ptr
are put in a linked list, there is no such implementation in practice. Additionally, the wording has been modified assuming, for instance, that theuse_count
is shared.The deleter is required to only move constructible. Thus, it is not possible to have several copies in the
shared_ptr
.
One can imagine an implementation which puts the deleter in a specially designed shared_ptr
and moves it when it the special shared_ptr
is deleted. While the implementation seems conformant, it is also strange, especially since a control block may be needed for the use count (it is perhaps possible but even weirder to do the same thing with the use count).
Relevant DRs I found: 545, 575, 2434 (which acknowledge that all implementations are using a control block and seem to imply that multi-threading constraints somewhat mandate it), 2802 (which requires that the deleter only move constructible and thus prevents implementation where the deleter is copied between several shared_ptr
's).
回答2:
From std::shared_ptr we have:
The control block is a dynamically-allocated object that holds:
- either a pointer to the managed object or the managed object itself;
- the deleter (type-erased);
- the allocator (type-erased);
- the number of shared_ptrs that own the managed object;
- the number of weak_ptrs that refer to the managed object.
And from std::allocate_shared we get:
template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );
Constructs an object of type T and wraps it in a std::shared_ptr [...] in order to use one allocation for both the control block of the shared pointer and the T object.
So it looks like std::allocate_shared should allocate the deleter
with your Alloc
.
EDIT: And from n4810
§20.11.3.6 Creation [util.smartptr.shared.create]
1 The common requirements that apply to all
make_shared
,allocate_shared
,make_shared_default_init
, andallocate_shared_default_init
overloads, unless specified otherwise, are described below.[...]
7 Remarks: (7.1) — Implementations should perform no more than one memory allocation. [Note: This provides efficiency equivalent to an intrusive smart pointer. —end note]
[Emphasis all mine]
So the standard is saying that std::allocate_shared
should use Alloc
for the control block.
回答3:
I believe this is unspecified.
Here's how the relevant constructors are specified: [util.smartptr.shared.const]/10
template<class Y, class D> shared_ptr(Y* p, D d); template<class Y, class D, class A> shared_ptr(Y* p, D d, A a); template <class D> shared_ptr(nullptr_t p, D d); template <class D, class A> shared_ptr(nullptr_t p, D d, A a);
Effects: Constructs a
shared_ptr
object that owns the objectp
and the deleterd
. WhenT
is not an array type, the first and second constructors enableshared_from_this
withp
. The second and fourth constructors shall use a copy ofa
to allocate memory for internal use. If an exception is thrown,d(p)
is called.
Now, my interpretation is that when the implementation needs memory for internal use, it does so by using a
. It doesn't mean that the implementation has to use this memory to place everything. For example, suppose that there's this weird implementation:
template <typename T>
class shared_ptr : /* ... */ {
// ...
std::aligned_storage<16> _Small_deleter;
// ...
public:
// ...
template <class _D, class _A>
shared_ptr(nullptr_t, _D __d, _A __a) // for example
: _Allocator_base{__a}
{
if constexpr (sizeof(_D) <= 16)
_Construct_at(&_Small_deleter, __d);
else
// use 'a' to allocate storage for the deleter
}
};
Does this implementation "use a copy of a
to allocate memory for internal use"? Yes, it does. It never allocates memory except by using a
. There are many problems with this naive implementation, but let's say that it switches to using allocators in all but the simplest case in which the shared_ptr
is constructed directly from a pointer and is never copied or moved or otherwise referenced and there are no other complications. The point is, just because we fail to imagine a valid implementation doesn't mean it can theoretically exist. I am not saying that such an implementation can actually be found, just that the standard doesn't seem to be actively prohibiting it.
来源:https://stackoverflow.com/questions/58932991/is-a-shared-ptrs-deleter-stored-in-memory-allocated-by-the-custom-allocator