std::make_shared, std::make_shared_for_overwrite
定义于头文件 <memory>
。
声明
// 1)
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args ); // T is not array
C++20
// 2)
template< class T >
shared_ptr<T> make_shared( std::size_t N ); // T is U[]
// 3)
template< class T >
shared_ptr<T> make_shared(); // T is U[N]
// 4)
template< class T >
shared_ptr<T> make_shared( std::size_t N, const std::remove_extent_t<T>& u ); // T is U[]
// 5)
template< class T >
shared_ptr<T> make_shared( const std::remove_extent_t<T>& u ); // T is U[N]
// 6)
template< class T >
shared_ptr<T> make_shared_for_overwrite(); // T is not U[]
// 7)
template< class T >
shared_ptr<T> make_shared_for_overwrite( std::size_t N ); // T is U[]
使用 args 作为 T 的构造函数的参数列表,构造一个类型为 T
的对象并将其包装在 std::shared_ptr
中。该对象通过表达式 ::new (pv) T(std::forward<Args>(args)...)
来构造,其中 pv
是一个内部的 void*
指针,指向适合容纳类型为 T
的对象的存储。该存储通常比 sizeof(T)
大,以便将共享指针的控制块和 T
对象放在一次分配中。此函数调用的 std::shared_ptr
构造函数可以使用新构造的类型为 T
的对象的指针来启用 shared_from_this
。此重载仅在 T
不是数组类型时参与重载解析。
2,3)
与 (1) 相同,但构造的对象是一个可能多维的数组,其类型为 std::remove_all_extents_t<T>
的非数组元素通过放置 new 表达式 ::new(pv) std::remove_all_extents_t<T>()
进行值初始化。重载 (2) 在第一维上创建一个大小为 N
的数组。数组元素按地址升序初始化,当它们的生命周期结束时,按照它们原始构造的相反顺序销毁。
4,5)
与 (2,3) 相同,但每个元素都从默认值 u
进行初始化。如果 U
不是数组类型,则执行方式与 (1) 中的相同放置 new 表达式相同;否则,则通过将 u
中的相应元素与 (1) 中的相同放置 new 表达式初始化(可能多维的)数组的每个非数组元素来执行。重载 (4) 在第一维上创建一个大小为 N
的数组。数组元素按地址升序初始化,当它们的生命周期结束时,按照它们原始构造的相反顺序销毁。
6)
如果 T
不是数组类型,则与 (1) 相同;如果 T
是 U[N]
,则与 (3) 相同,但创建的对象是默认初始化的。
7)
与 (2) 相同,但各个数组元素是默认初始化的。
在每种情况下,对象(如果 T
是数组类型,则为各个元素)都将通过 p->~X()
进行销毁,其中 p
是指向对象的指针,X
是其类型。
参数
args
- 用于构造 T
实例的参数列表。N
- 用于数组的大小 u
- 用于初始化数组每个元素的初始值。
返回值
类型为 T
的实例的 std::shared_ptr
。
异常
可能抛出 std::bad_alloc
或 T
的构造函数抛出的任何异常。如果抛出异常,则函数无效。如果在数组构造期间抛出异常,则已初始化的元素将按相反顺序销毁。
备注
此函数可作为 std::shared_ptr<T>(new T(args...))
的替代方案。其权衡如下:
std::shared_ptr<T>(new T(args...))
至少执行两次分配(一次用于对象T
,一次用于共享指针的控制块),而std::make_shared<T>
通常只执行一次分配(标准推荐但非强制要求;所有已知实现都这样做)。- 如果在所有共享所有者生命周期结束后,任何
std::weak_ptr
引用了std::make_shared
创建的控制块,则T
占用的内存将一直保留,直到所有弱所有者也被销毁为止,如果sizeof(T)
很大,这可能是不希望的。 std::shared_ptr<T>(new T(args...))
如果在可访问的上下文中执行,可能会调用 T 的非公共构造函数,而std::make_shared
需要对选定的构造函数具有公共访问权限。- 与
std::shared_ptr
构造函数不同,std::make_shared
不允许自定义删除器。 std::make_shared
使用::new
,因此如果通过类特定的 operator new 设置了任何特殊行为,它将与std::shared_ptr<T>(new T(args...))
不同。
直到 C++20
std::shared_ptr
支持数组类型(从 C++17 开始),但std::make_shared
不支持。此功能由boost::make_shared
支持。
直到 C++17
- 像
f(std::shared_ptr<int>(new int(42)), g())
这样的代码可能导致内存泄漏,如果g
在new int(42)
之后被调用并抛出异常,而f(std::make_shared<int>(42), g())
是安全的,因为两个函数调用永远不会交错。
构造函数通过指针 ptr(类型为 U*
)启用 shared_from_this
,这意味着它会确定 U
是否具有可访问的(自 C++17 起)明确的基类,该基类是 std::enable_shared_from_this
的特化,如果是,则构造函数将评估语句
if (ptr != nullptr && ptr->weak_this.expired())
ptr->weak_this = std::shared_ptr<std::remove_cv_t<U>>(
*this, const_cast<std::remove_cv_t<U>*>(ptr));
其中 weak_this
是 std::enable_shared_from_this
的隐藏可变 std::weak_ptr
成员。对 weak_this
成员的赋值不是原子的,并且会与对同一对象的任何潜在并发访问发生冲突。这确保了将来对 shared_from_this()
的调用将与此原始指针构造函数创建的 std::shared_ptr
共享所有权。
上述示例代码中的测试 ptr->weak_this.expired()
确保如果 weak_this
已经指示了一个所有者,它不会被重新赋值。此测试自 C++17 起是必需的。
特性测试宏 | 值 | 标准 | 注释 |
---|---|---|---|
__cpp_lib_shared_ptr_arrays | 201707L | (C++20) | std::make_shared 的数组支持,用于重载 (2-5)。 |
__cpp_lib_smart_ptr_for_overwrite | 202002L | (C++20) | 智能指针创建带默认初始化,用于重载 (6,7)。 |
示例
#include <memory>
#include <vector>
#include <iostream>
#include <type_traits>
struct C
{
// constructors needed (until C++20)
C(int i) : i(i) {}
C(int i, float f) : i(i), f(f) {}
int i;
float f{};
};
int main()
{
// using `auto` for the type of `sp1`
auto sp1 = std::make_shared<C>(1); // overload (1)
static_assert(std::is_same_v<decltype(sp1), std::shared_ptr<C>>);
std::cout << "sp1->{ i:" << sp1->i << ", f:" << sp1->f << " }\n";
// being explicit with the type of `sp2`
std::shared_ptr<C> sp2 = std::make_shared<C>(2, 3.0f); // overload (1)
static_assert(std::is_same_v<decltype(sp2), std::shared_ptr<C>>);
static_assert(std::is_same_v<decltype(sp1), decltype(sp2)>);
std::cout << "sp2->{ i:" << sp2->i << ", f:" << sp2->f << " }\n";
// shared_ptr to a value-initialized float[64]; overload (2):
std::shared_ptr<float[]> sp3 = std::make_shared<float[]>(64);
// shared_ptr to a value-initialized long[5][3][4]; overload (2):
std::shared_ptr<long[][3][4]> sp4 = std::make_shared<long[][3][4]>(5);
// shared_ptr to a value-initialized short[128]; overload (3):
std::shared_ptr<short[128]> sp5 = std::make_shared<short[128]>();
// shared_ptr to a value-initialized int[7][6][5]; overload (3):
std::shared_ptr<int[7][6][5]> sp6 = std::make_shared<int[7][6][5]>();
// shared_ptr to a double[256], where each element is 2.0; overload (4):
std::shared_ptr<double[]> sp7 = std::make_shared<double[]>(256, 2.0);
// shared_ptr to a double[7][2], where each double[2]
// element is {3.0, 4.0}; overload (4):
std::shared_ptr<double[][2]> sp8 = std::make_shared<double[][2]>(7, {3.0, 4.0});
// shared_ptr to a vector<int>[4], where each vector
// has contents {5, 6}; overload (4):
std::shared_ptr<std::vector<int>[]> sp9 =
std::make_shared<std::vector<int>[]>(4, {5, 6});
// shared_ptr to a float[512], where each element is 1.0; overload (5):
std::shared_ptr<float[512]> spA = std::make_shared<float[512]>(1.0);
// shared_ptr to a double[6][2], where each double[2] element
// is {1.0, 2.0}; overload (5):
std::shared_ptr<double[6][2]> spB = std::make_shared<double[6][2]>({1.0, 2.0});
// shared_ptr to a vector<int>[4], where each vector
// has contents {5, 6}; overload (5):
std::shared_ptr<std::vector<int>[4]> spC =
std::make_shared<std::vector<int>[4]>({5, 6});
}
sp1->{ i:1, f:0 }
sp2->{ i:2, f:3 }