This paper proposes adding array support to make_shared
, via the syntax
make_shared<T[]>
and make_shared<T[N]>
.
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 make_shared
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]
Programmers like make_shared
. 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.
Boost release 1.53, available from www.boost.org, contains an implementation of an earlier revision of this proposal. The Boost distribution contains tests and documentation, which can be browsed online.
A shared_ptr
that supports arrays is a dependency that has been proposed
in a separate paper (N3869).
The proposed wording also resolves library issue #2070.
make_shared_noinit
It is not uncommon for arrays of built-in types such as unsigned char
or double
to be immediately initialized by the user in their entirety
after allocation. In these cases, the value initialization performed by make_shared
is redundant and hurts performance, and a way to choose default initialization is needed.
This proposal suggests make_shared_noinit
and allocate_shared_noinit
as a way to perform default initialization on the elements. The suffix _noinit
,
instead of something derived from "default", has been chosen because the use cases
of this overload always deal with either uninitialized elements (when the type is
a built-in) or with potentially uninitialized elements (when the type is dependent
on a template parameter in a generic function and may be a built-in). Typically,
therefore, the programmer assumes that after make_shared_noinit
, the
elements are uninitialized, that is, hold unspecified values, and the _noinit
name reflects this assumption.
Construction using constructor arguments — T(a, b)
— is enough
for the majority of cases, but there are scenarios in which one would like to use
T{a, b}
instead. Aggregates, as one example, can only be initialized
with {}
; standard containers, as another, support both T(a, b)
and T{a, b}
, but give the two forms different meanings.
This proposal suggests the addition of a (scalar) overload that supports the syntax
make_shared<T>({a, b})
as a way to produce an object initialized with
T{a, b}
.
(If Args&&... args
could take {a, b}
and forward
it perfectly, we wouldn't need another overload to support the above syntax. It
cannot, so we do. Its implementation is essentially the same as that of the Args
overload.)
(All edits are relative to N3485.)
Change 20.7.2.2 [util.smartptr.shared] p1 as follows:
// 20.7.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(T&& t); // T is not array template<class T, class A> shared_ptr<T> allocate_shared(const A& a, T&& t); // 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 U& u); // T is U[] template<class T, class A> shared_ptr<T> allocate_shared(const A& a, size_t N, const U& u); // T is U[] template<class T> shared_ptr<T> make_shared(const U& u); // T is U[N] template<class T, class A> shared_ptr<T> allocate_shared(const A& a, const U& u); // T is U[N] template<class T> shared_ptr<T> make_shared_noinit(); // T is not U[] template<class T, class A> shared_ptr<T> allocate_shared_noinit(const A& a); // T is not U[] template<class T> shared_ptr<T> make_shared_noinit(size_t N); // T is U[] template<class T, class A> shared_ptr<T> allocate_shared_noinit(const A& a, size_t N); // T is U[]
Replace the contents 20.7.2.2.6 [util.smartptr.shared.create] with the following:
The common requirements that apply to allmake_shared
,allocate_shared
,make_shared_noinit
, andallocate_shared_noinit
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); template<class T, ...> shared_ptr<T> make_shared_noinit(args); template<class T, class A, ...> shared_ptr<T> allocate_shared_noinit(const A& a, args);Requires:A
shall be an allocator (17.6.3.5). The copy constructor and destructor ofA
shall not throw exceptions.Effects: Allocates memory for an object of typeT
(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. The templatesallocate_shared
andallocate_shared_noinit
use a copy ofa
to allocate memory. If an exception is thrown, the functions have no effect.Returns: Ashared_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 fromA::allocate
, 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 typeU
is specified to be initialized to a value of the same typeu
, this shall be interpreted to mean that each array element of the object is initialized to the corresponding element fromu
.When an object of an array type is specified to be value-initialized, this shall be interpreted to mean that each array element of the object is value-initialized.When a (sub)object of a non-array typeU
is specified to be initialized to a valuev
, or toU(l...)
, wherel...
is a list of constructor arguments,make_shared
shall perform this initialization 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 non-array typeU
is specified to be initialized to a valuev
, or toU(l...)
, wherel...
is a list of constructor arguments,allocate_shared
shall perform this initialization 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 typeU
is specified to be value-initialized,make_shared
shall perform this initialization 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 typeU
is specified to be value-initialized,allocate_shared
shall perform this initialization 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
.When a (sub)object of non-array typeU
is specified to be default-initialized,make_shared_noinit
andallocate_shared_noinit
shall perform this initialization via the expression::new(pv) U
, wherepv
has typevoid*
and points to storage suitable to hold an object of typeU
.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 thansizeof(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 arrayReturns: Ashared_ptr
to an object of typeT
, initialized toT(forward<Args>(args)...)
.Remarks: These overloads shall only participate in overload resolution whenT
is not an array type.[ 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(T&& t); // T is not array template<class T, class A> shared_ptr<T> allocate_shared(const A& a, T&& t); // T is not arrayReturns: Ashared_ptr
to an object of typeT
, initialized tomove(t)
.Remarks: These overloads shall only participate in overload resolution whenT
is not an array type.[ Example:shared_ptr<pair<string,int>> p = make_shared<pair<string,int>>({"test",2}); // shared_ptr to pair{"test",2}shared_ptr<vector<int>> q = make_shared<vector<int>>({16,1}); // shared_ptr to vector with contents {16,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: Ashared_ptr
to a value-initialized object of typeU[N]
.Remarks: These overloads shall only participate in overload resolution whenT
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: Ashared_ptr
to a value-initialized object of typeU[N]
.Remarks: These overloads shall only participate in overload resolution whenT
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 U& u); // T is U[] template<class T, class A> shared_ptr<T> allocate_shared(const A& a, size_t N, const U& u); // T is U[]Returns: Ashared_ptr
to an object of typeU[N]
, where each array element of typeU
is initialized tou
.Remarks: These overloads shall only participate in overload resolution whenT
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.0shared_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 U& u); // T is U[N] template<class T, class A> shared_ptr<T> allocate_shared(const A& a, const U& u); // T is U[N]Returns: Ashared_ptr
to an object of typeU[N]
, where each array element of typeU
is initialized tou
.Remarks: These overloads shall only participate in overload resolution whenT
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.0shared_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 ].
template<class T> shared_ptr<T> make_shared_noinit(); // T is not U[] template<class T, class A> shared_ptr<T> allocate_shared_noinit(const A& a); // T is not U[]Returns: Ashared_ptr
to a default-initialized object of typeT
.Remarks: These overloads shall only participate in overload resolution whenT
is not of the formU[]
.[ Example:struct X { double data[1024]; };shared_ptr<X> p = make_shared_noinit<X>(); // shared_ptr to a default-initialized X, with X::data left uninitializedshared_ptr<double[1024]> q = make_shared_noinit<double[1024]>(); // shared_ptr to a default-initialized double[1024], with the elements left unitialized— end example ].
template<class T> shared_ptr<T> make_shared_noinit(size_t N); // T is U[] template<class T, class A> shared_ptr<T> allocate_shared_noinit(const A& a, size_t N); // T is U[]Returns: Ashared_ptr
to a default-initialized object of typeU[N]
.Remarks: These overloads shall only participate in overload resolution whenT
is of the formU[]
.[ Example:shared_ptr<double[]> p = make_shared_noinit<double[]>(1024); // shared_ptr to a default-initialized double[1024], with the elements left unitialized— end example ].
— end