Issue 2070: allocate_shared should use allocator_traits::construct (original) (raw)
This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.
2070. 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 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
Tand constructs an object in that memory via the placement new expression::new (pv) T(std::forward<Args>(args)...). The templateallocate_shareduses a copy of a to allocate memory. If an exception is thrown, the functions have no effect.
This explicitly requires placement new rather than usingallocator_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 andallocate_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 make_shared(Args&&... args);
template<class T, class A, class... Args>
shared_ptr allocate_shared(const A& a, Args&&... args);-1- Requires: For the template
make_shared, tThe expression::new (pv) T(std::forward<Args>(args)...), wherepvhas 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)...), wherepthas typeT*and points to storage suitable to hold an object of typeT, shall be well formed.Ashall be an allocator ([allocator.requirements]).The copy constructor and destructor ofAshall not throw exceptions.-2- Effects: Allocates memory suitable for an object of type
Tand constructs an object in that memory. The templatemake_sharedconstructs the object via the placement new expression::new (pv) T(std::forward<Args>(args)...). The templateallocate_shareduses a copy ofato 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: A
shared_ptrinstance 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::allocateor fromallocator_traits<A>::constructfrom the constructor of.T-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 than
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(i).
[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 make_shared(Args&&... args);
template<class T, class A, class... Args>shared_ptr allocate_shared(const A& a, Args&&... args);-1- Requires: The expression::new (pv) T(std::forward<Args>(args)...), wherepvhas typevoid*and points to storage suitable to hold an object of typeT, shall be well formed.Ashall be an allocator (16.4.4.6 [allocator.requirements]). The copy constructor and destructor ofAshall not throw exceptions.
-2- Effects: Equivalent toreturn allocate_shared(allocator(), std::forward(args)...);
Allocates memory suitable for an object of typeTand constructs an object in that memory via the placement new expression::new (pv) T(std::forward<Args>(args)...). The templateallocate_shareduses a copy ofato allocate memory. If an exception is thrown, the functions have no effect.
-?- Remarks: An implementation may meet the effects (and the implied guarantees) without creating the allocator object [Note: That is, user-provided specializations ofstd::allocatormay not be instantiated, the expressions::new (pv) T(std::forward<Args>(args)...)andpv->~T()may be evaluated directly — _end note_].-3- Returns: Ashared_ptrinstance 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 an exception thrown fromA::allocateor from the constructor ofT.-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 thansizeof(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 allocate_shared(const A& a, Args&&... args);
-?- Requires: The expressionsallocator_traits<A>::construct(b, pt, std::forward<Args>(args)...)andallocator_traits<A>::destroy(b, pt)shall be well-formed and well-defined, wherebhas typeAand is a copy ofaand wherepthas typeT*and points to storage suitable to hold an object of typeT.Ashall meet the allocator requirements (16.4.4.6 [allocator.requirements]).
-?- Effects: Uses an objecta2of typeallocator_traits<A>::rebind_alloc<_unspecified_>that compares equal toato allocate memory suitable for an object of typeT. Uses a copybof typeAfromato construct an object of typeTin that memory by callingallocator_traits<A>::construct(b, pt, std::forward<Args>(args)...). If an exception is thrown, the function has no effect.
-?- Returns: Ashared_ptrinstance that stores and owns the address of the newly constructed object of typeT. When ownership is given up, the effects are as follows: Uses a copyb2of typeAfromato destruct an object of typeTby callingallocator_traits<A>::destroy(b2, pt2)wherept2has typeT*and refers to the newly constructed object. Then uses an object of typeallocator_traits<A>::rebind_alloc<_unspecified_>that compares equal toato deallocate the allocated memory.
-?- Postconditions:get() != 0 && use_count() == 1
-?- Throws: Nothing unless memory allocation orallocator_traits<A>::constructthrows 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 thansizeof(T)to allow for internal bookkeeping structures such as the reference counts. — _end note_]