allocate_shared
should use allocator_traits<A>::construct
Section: 20.3.2.2.7 [util.smartptr.shared.create] Status: Resolved Submitter: Jonathan Wakely Opened: 2011-07-11 Last modified: 2017-07-16
Priority: 2
View other active issues in [util.smartptr.shared.create].
View all other issues in [util.smartptr.shared.create].
View all issues with Resolved status.
Discussion:
20.3.2.2.7 [util.smartptr.shared.create] says:
-2- Effects: Allocates memory suitable for an object of type
T
and constructs an object in that memory via the placement new expression::new (pv) T(std::forward<Args>(args)...)
. The templateallocate_shared
uses a copy of a to allocate memory. If an exception is thrown, the functions have no effect.
This explicitly requires placement new rather than using
allocator_traits<A>::construct(a, (T*)pv, std::forward<Args>(args)...)
In most cases that would result in the same placement new expression,
but would allow more control over how the object is constructed e.g.
using scoped_allocator_adaptor
to do uses-allocator construction, or
using an allocator declared as a friend to construct objects with no
public constructors.
[2011-08-16 Bloomington:]
Agreed to fix in principle, but believe that make_shared
and
allocate_shared
have now diverged enough that their descriptions
should be separated. Pablo and Stefanus to provide revised wording.
Daniel's (old) proposed resolution:
This wording is relative to the FDIS.
Change the following paragraphs of 20.3.2.2.7 [util.smartptr.shared.create] as indicated (The suggested removal of the last sentence of p1 is not strictly required to resolve this issue, but is still recommended, because it does not say anything new but may give the impression that it says something new):
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args); template<class T, class A, class... Args> shared_ptr<T> allocate_shared(const A& a, Args&&... args);-1- Requires: For the template
-2- Effects: Allocates memory suitable for an object of typemake_shared
, tThe expression::new (pv) T(std::forward<Args>(args)...)
, wherepv
has typevoid*
and points to storage suitable to hold an object of typeT
, shall be well formed. For the templateallocate_shared
, the expressionallocator_traits<A>::construct(a, pt, std::forward<Args>(args)...)
, wherept
has typeT*
and points to storage suitable to hold an object of typeT
, shall be well formed.A
shall be an allocator ([allocator.requirements]).The copy constructor and destructor ofA
shall not throw exceptions.T
and constructs an object in that memory. The templatemake_shared
constructs the object via the placement new expression::new (pv) T(std::forward<Args>(args)...)
. The templateallocate_shared
uses a copy ofa
to allocate memory and constructs the object by callingallocator_traits<A>::construct(a, pt, std::forward<Args>(args)...)
. If an exception is thrown, the functions have no effect. -3- Returns: Ashared_ptr
instance that stores and owns the address of the newly constructed object of typeT
. -4- Postconditions:get() != 0 && use_count() == 1
-5- Throws:bad_alloc
, or, for the templatemake_shared
, an exception thrown from the constructor ofT
, or, for the templateallocate_shared
, an exception thrown fromA::allocate
or fromallocator_traits<A>::construct
from the constructor of. -6- Remarks: Implementations are encouraged, but not required, to perform no more than one memory allocation. [ Note: This provides efficiency equivalent to an intrusive smart pointer. — end note ] -7- [ Note: These functions will typically allocate more memory thanT
sizeof(T)
to allow for internal bookkeeping structures such as the reference counts. — end note ]
[2011-12-04: Jonathan and Daniel improve wording]
See also c++std-lib-31796
[2013-10-13, Ville]
This issue is related to 2089.
[2014-02-15 post-Issaquah session : move to Tentatively NAD]
STL: This takes an allocator, but then ignores its construct. That's squirrely.
Alisdair: The convention is when you take an allocator, you use its construct.
STL: 23.2.2 [container.requirements.general]/3, argh! This fills me with despair, but I understand it now.
STL: Ok, this is some cleanup.
STL: You're requiring b
to be of type A
and not being rebound, is that an overspecification?
Pablo: Good point. Hmm, that's only a requirement on what must be well-formed.
STL: If it's just a well-formed requirement, then why not just use a directly?
Pablo: Yeah, the well-formed requirement is overly complex. It's not a real call, we could just use a directly. It makes it harder to read.
Alisdair: b
should be an allocator in the same family as a
.
Pablo: This is a well-formed requirement, I wonder if it's the capital A that's the problem here. It doesn't matter here, this is way too much wording.
Alisdair: It's trying to tie the constructor arguments into the allocator requirements.
Pablo: b
could be struck, that's a runtime quality. The construct will work with anything that's in the family of A
.
Alisdair: The important part is the forward
of Args
.
Pablo: A
must be an allocator, and forward
Args
must work with that.
Alisdair: First let's nail down A
.
Pablo: Then replace b
with a
, and strike the rest.
STL: You need pt
's type, at least.
Pablo: There's nothing to be said about runtime constraints here, this function doesn't even take a pt
.
STL: Looking at the Effects, I believe b
is similarly messed up, we can use a2
to construct an object.
Alisdair: Or any allocator in the family of a
.
STL: We say this stuff for the deallocate too, it should be lifted up.
STL: "owns the address" is weird.
Alisdair: shared_ptr owns pointers, although it does sound funky.
Walter: "to destruct" is ungrammatical.
STL: "When ownership is given up" is not what we usually say.
Alisdair: I think the Returns clause is the right place to say this.
STL: The right place to say this is shared_ptr
's dtor, we don't want to use Core's "come from" convention.
Alisdair: I'm on the hook to draft cleaner wording.
[2015-10, Kona Saturday afternoon]
AM: I was going to clean up the wording, but haven't done it yet.
Defer until we have new wording.
[2016-03, Jacksonville]
Alisdair: we need to figure out whether we should call construct or not; major implementation divergence
STL: this does not grant friendship, does it?
Jonathan: some people want it.
Thomas: scoped allocator adapter should be supported, so placement new doesn't work
Alisdair: this makes the make_ functions impossible
Thomas: you don't want to use those though.
Alisdair: but people use that today, at Bloomberg
Alisdair: and what do we do about fancy pointers?
Jonathan: we constrain it to only non-fancy pointers.
STL: shared_ptr has never attempted to support fancy pointers; seems like a paper is needed.
Poll: call construct:6 operator new: 0 don't care: 4
Poll: should we support fancy pointers? Yes: 1 No: 4 don't care: 4
STL: 20.8.2.2.6p2: 'and pv->~T()' is bogus for void
STL: 20.8.2.2.6p4: is this true even if we're going to allocate a bit more?
Alisdair: yes
Alisdair: coming up with new wording
[2016-08, Chicago Monday PM]
Alisdair to provide new wording this week
[2017-07 Toronto]
Resolved by the adoption of P0674R1 in Toronto
Proposed resolution:
This wording is relative to the FDIS.
Change the following paragraphs of 20.3.2.2.7 [util.smartptr.shared.create] as indicated:
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);template<class T, class A, class... Args> shared_ptr<T> allocate_shared(const A& a, Args&&... args);
-1- Requires: The expression
::new (pv) T(std::forward<Args>(args)...)
, where pv
has type void*
and points to storage suitable to hold an object of type T
, shall be well
formed. A
shall be an allocator (16.4.4.6 [allocator.requirements]). The copy constructor
and destructor of A
shall not throw exceptions.
return allocate_shared<T>(allocator<T>(), std::forward<Args>(args)...);
Allocates memory suitable for an object of type
T
and constructs an object in that memory via the placement new expression
::new (pv) T(std::forward<Args>(args)...)
. The template allocate_shared
uses a copy
of a
to allocate memory. If an exception is thrown, the functions have no effect.
std::allocator
may not be instantiated, the expressions ::new (pv) T(std::forward<Args>(args)...)
and
pv->~T()
may be evaluated directly — end note].
shared_ptr
instance that stores and owns the address of the newly constructed
object of type T
.get() != 0 && use_count() == 1
bad_alloc
, or an exception thrown from A::allocate
or from the
constructor of T
.sizeof(T)
to allow
for internal bookkeeping structures such as the reference counts. — end note]Add the following set of new paragraphs immediately following the previous paragraph 7 of 20.3.2.2.7 [util.smartptr.shared.create]:
template<class T, class A, class... Args> shared_ptr<T> allocate_shared(const A& a, Args&&... args);
-?- Requires: The expressions
allocator_traits<A>::construct(b, pt, std::forward<Args>(args)...)
and
allocator_traits<A>::destroy(b, pt)
shall be well-formed and well-defined,
where b
has type A
and is a copy of a
and where pt
has type T*
and points to storage suitable to hold an object of type T
.
A
shall meet the allocator requirements (16.4.4.6 [allocator.requirements]).
a2
of type allocator_traits<A>::rebind_alloc<unspecified>
that compares equal to
a
to allocate memory suitable for an object of type T
.
Uses a copy b
of type A
from a
to construct an object of type T
in
that memory by calling allocator_traits<A>::construct(b, pt, std::forward<Args>(args)...)
.
If an exception is thrown, the function has no effect.
-?- Returns: A shared_ptr
instance that stores and owns the address of the newly constructed
object of type T
. When ownership is given up, the effects are as follows: Uses a copy b2
of type A
from a
to destruct an object of type T
by calling
allocator_traits<A>::destroy(b2, pt2)
where pt2
has type T*
and refers to the newly constructed object. Then uses an object of type
allocator_traits<A>::rebind_alloc<unspecified>
that compares equal to
a
to deallocate the allocated memory.
-?- Postconditions: get() != 0 && use_count() == 1
-?- Throws: Nothing unless memory allocation or allocator_traits<A>::construct
throws an exception.
-?- Remarks: Implementations are encouraged, but not required, to perform no more than one memory
allocation. [Note: Such an implementation provides efficiency equivalent to an intrusive smart
pointer. — end note]
-?- [Note: This function will typically allocate more memory than sizeof(T)
to allow for internal
bookkeeping structures such as the reference counts. — end note]