Document Number |
P0674R1 |
Date |
2017-07-12 |
Project |
Programming Language C++ |
Audience |
Library Evolution Working Group |
Summary |
This paper proposes adding array support to |
Revision History
Changes in 0674R1
This revision of P0674R0 replaces the use of initialized to and value-initialized to avoid conflict with the core language terms.
Changes in Revision 3
This revision of N3939 removes the _noinit
overloads, performs some slight modifications to the proposed text, and adds a
wording rationale section.
Changes in Revision 2
This revision of N3870 removes the scalar T&&
overloads, reflecting the result of the LEWG review.
Changes in Revision 1
This revision of N3641 significantly narrows the scope of the proposal, based on feedback from Jeffrey Yasskin and Stephan T. Lavavej. It cuts down the number of overloads considerably, leaving only two use cases:
-
Value initialization, analogous to
new U[N]()
:template<class T> shared_ptr<T> make_shared(size_t N); // T is U[]
template<class T> shared_ptr<T> make_shared(); // T is U[N]
-
Per-element initialization to a specified value, analogous to the
std::vector<U>(N, u)
constructor:template<class T> shared_ptr<T> make_shared(size_t N, const U& u); // T is U[]
template<class T> shared_ptr<T> make_shared(const U& u); // T is U[N]
Motivation
make_shared
is consistently recommended as the preferred way to create objects managed by shared_ptr
. It delivers exception safety,
is easier to type, and saves one allocation (and a few bytes in the control block). Not surprisingly, a very common request is for it to
support arrays.
An implementation of this proposal has been available from Boost since release 1.56 and is in wide use.
The proposed wording also resolves library issue #2070.
Wording Rationale
The proposed text contains what some may consider redefinitions of the standard terms initialized to and value-initialized, and a lot of seemingly unnecessary complexity.
This is all needed in order to properly initialize arrays due to the irregular behavior of the standard new-expression. When the type
T
in the expression ::new (pv) T()
is an array type, what happens is not, as in the scalar case, an object of type T
being
constructed at the address pv
. When T
is for instance U[5]
, the expression turns into the array form of new
, which means that
it calls operator new[]
instead and may prepend a length prefix to the array elements. The end result is that code cannot assume
that after the new-expression pv
points to an object of type T
, that is, U[5]
— because it may not.
For this reason, the wording below does not define initialization as just ::new T
. Instead, the array initializations are specifically
redefined to consist of initializations of every element. Only for scalar types does the wording invoke ::new T
(or construct
in
the allocator case.)
Proposed Text
-
// 23.11.2.2.6, shared_ptr creation template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args); // T is not array template<class T, class A, class... Args> shared_ptr<T> allocate_shared(const A& a, Args&&... args); // T is not array template<class T> shared_ptr<T> make_shared(size_t N); // T is U[] template<class T, class A> shared_ptr<T> allocate_shared(const A& a, size_t N); // T is U[] template<class T> shared_ptr<T> make_shared(); // T is U[N] template<class T, class A> shared_ptr<T> allocate_shared(const A& a); // T is U[N] template<class T> shared_ptr<T> make_shared(size_t N, const remove_extent_t<T>& u); // T is U[] template<class T, class A> shared_ptr<T> allocate_shared(const A& a, size_t N, const remove_extent_t<T>& u); // T is U[] template<class T> shared_ptr<T> make_shared(const remove_extent_t<T>& u); // T is U[N] template<class T, class A> shared_ptr<T> allocate_shared(const A& a, const remove_extent_t<T>& u); // T is U[N]
-
The common requirements that apply to all
make_shared
andallocate_shared
overloads, unless specified otherwise, are described below.template<class T, ...> shared_ptr<T> make_shared(args);
template<class T, class A, ...> shared_ptr<T> allocate_shared(const A& a, args);
Requires: A shall be an allocator (20.5.3.5).
Effects: Allocates memory for an object of type
T
(orU[N]
whenT
isU[]
, whereN
is determined fromargs
as specified by the concrete overload). The object is initialized fromargs
as specified by the concrete overload. Theallocate_shared
templates use a copy ofa
(rebound for an unspecifiedvalue_type
) to allocate memory. If an exception is thrown, the functions have no effect.Returns: A
shared_ptr
instance that stores and owns the address of the newly constructed object.Postconditions: r.get() != 0 && r.use_count() == 1
, wherer
is the return value.Throws: bad_alloc
, an exception thrown fromallocate
, or from the initialization of the object.Remarks: -
Implementations should perform no more than one memory allocation. [ Note: This provides efficiency equivalent to an intrusive smart pointer. -- end note ].
-
When an object of an array type
U
is specified to have an initial value ofu
(of the same type), this shall be interpreted to mean that each array element of the object has as its initial value the corresponding element fromu
. -
When an object of an array type is specified to have a default initial value, this shall be interpreted to mean that each array element of the object has a default initial value.
-
When a (sub)object of a non-array type
U
is specified to have an initial value ofv
, orU(l...)
, wherel...
is a list of constructor arguments,make_shared
shall initialize this (sub)object via the expression::new(pv) U(v)
or::new(pv) U(l...)
respectively, wherepv
has typevoid*
and points to storage suitable to hold an object of typeU
. -
When a (sub)object of a non-array type
U
is specified to have an initial value ofv
, orU(l...)
, wherel...
is a list of constructor arguments,allocate_shared
shall initialize this (sub)object via the expressionallocator_traits<A2>::construct(a2, pv, v)
orallocator_traits<A2>::construct(a2, pv, l...)
respectively, wherepv
points to storage suitable to hold an object of typeU
anda2
of typeA2
is a rebound copy of the allocatora
passed toallocate_shared
such that itsvalue_type
isU
. -
When a (sub)object of non-array type
U
is specified to have a default initial value,make_shared
shall initialize this (sub)object via the expression::new(pv) U()
, wherepv
has typevoid*
and points to storage suitable to hold an object of typeU
. -
When a (sub)object of non-array type
U
is specified to have a default initial value,allocate_shared
shall initialize this (sub)object via the expressionallocator_traits<A2>::construct(a2, pv)
, wherepv
points to storage suitable to hold an object of typeU
anda2
of typeA2
is a rebound copy of the allocatora
passed toallocate_shared
such that itsvalue_type
isU
. -
Array elements are initialized in ascending order of their addresses.
-
When the lifetime of the object managed by the return value ends, or when the initialization of an array element throws an exception, the initialized elements should be destroyed in the reverse order of their construction.
[ Note: These functions will typically allocate more memory than
sizeof(T)
to allow for internal bookkeeping structures such as the reference counts. -- end note ].
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args); // T is not array
template<class T, class A, class... Args> shared_ptr<T> allocate_shared(const A& a, Args&&... args); // T is not array
Returns: A
shared_ptr
to an object of typeT
with an initial valueT(forward<Args>(args)...)
.Remarks: These overloads shall only participate in overload resolution when
T
is not an array type. Theshared_ptr
constructors called by these functions enableshared_from_this
with the address of the newly constructed object of typeT
.[ Example:
shared_ptr<int> p = make_shared<int>(); // shared_ptr to int()
shared_ptr<vector<int>> q = make_shared<vector<int>>(16, 1); // shared_ptr to vector of 16 elements with value 1
-- end example ].
template<class T> shared_ptr<T> make_shared(size_t N); // T is U[]
template<class T, class A> shared_ptr<T> allocate_shared(const A& a, size_t N); // T is U[]
Returns: A
shared_ptr
to an object of typeU[N]
with a default initial value, whereU
isremove_extent_t<T>
.Remarks: These overloads shall only participate in overload resolution when
T
is of the formU[]
.[ Example:
shared_ptr<double[]> p = make_shared<double[]>(1024); // shared_ptr to a value-initialized double[1024]
shared_ptr<double[][2][2]> q = make_shared<double[][2][2]>(6); // shared_ptr to a value-initialized double[6][2][2]
-- end example ].
template<class T> shared_ptr<T> make_shared(); // T is U[N]
template<class T, class A> shared_ptr<T> allocate_shared(const A& a); // T is U[N]
Returns: A
shared_ptr
to an object of typeT
with a default initial value.Remarks: These overloads shall only participate in overload resolution when
T
is of the formU[N]
.[ Example:
shared_ptr<double[1024]> p = make_shared<double[1024]>(); // shared_ptr to a value-initialized double[1024]
shared_ptr<double[6][2][2]> q = make_shared<double[6][2][2]>(); // shared_ptr to a value-initialized double[6][2][2]
-- end example ].
template<class T> shared_ptr<T> make_shared(size_t N, const remove_extent_t<T>& u); // T is U[]
template<class T, class A> shared_ptr<T> allocate_shared(const A& a, size_t N, const remove_extent_t<T>& u); // T is U[]
Returns: A
shared_ptr
to an object of typeU[N]
, whereU
isremove_extent_t<T>
and each array element has an initial value ofu
.Remarks: These overloads shall only participate in overload resolution when
T
is of the formU[]
.[ Example:
shared_ptr<double[]> p = make_shared<double[]>(1024, 1.0); // shared_ptr to a double[1024], where each element is 1.0
shared_ptr<double[][2]> q = make_shared<double[][2]>(6, {1.0, 0.0}); // shared_ptr to a double[6][2], where each double[2] element is {1.0, 0.0}
shared_ptr<vector<int>[]> r = make_shared<vector<int>[]>(4, {1, 2}); // shared_ptr to a vector<int>[4], where each vector has contents {1, 2}
-- end example ].
template<class T> shared_ptr<T> make_shared(const remove_extent_t<T>& u); // T is U[N]
template<class T, class A> shared_ptr<T> allocate_shared(const A& a, const remove_extent_t<T>& u); // T is U[N]
Returns: A
shared_ptr
to an object of typeT
, where each array element of typeremove_extent_t<T>
has an initial value ofu
.Remarks: These overloads shall only participate in overload resolution when
T
is of the formU[N]
.[ Example:
shared_ptr<double[1024]> p = make_shared<double[1024]>(1.0); // shared_ptr to a double[1024], where each element is 1.0
shared_ptr<double[6][2]> q = make_shared<double[6][2]>({1.0, 0.0}); // shared_ptr to a double[6][2], where each double[2] element is {1.0, 0.0}
shared_ptr<vector<int>[4]> r = make_shared<vector<int>[4]>({1, 2}); // shared_ptr to a vector<int>[4], where each vector has contents {1, 2}
-- end example ].
-