共享所有权(stdshared ptr)
类模板 std::shared_ptr
定义了一个共享指针,该指针能够与其他共享指针共享对象的所有权。这与代表独家所有权的 std::unique_ptr
形成对比。
共享行为通过称为引用计数的技术实现,其中指向对象的共享指针的数量与其一起存储。当此计数达到零时,无论是通过销毁还是重新分配最后一个 std::shared_ptr
实例,对象都会自动销毁。
// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);
要创建共享同一对象的多个智能指针,我们需要创建另一个使第一个共享指针别名的 shared_ptr
。这有两种方法:
std::shared_ptr<Foo> secondShared(firstShared); // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared; // 2nd way: Assigning
上述两种方法都使得 secondShared
成为共享指针,与 firstShared
共享我们的 Foo
实例的所有权。
智能指针就像原始指针一样工作。这意味着,你可以使用*
取消引用它们。常规的 ->
运算符也可以运行:
secondShared->test(); // Calls Foo::test()
最后,当最后一个别名 shared_ptr
超出范围时,我们的 Foo
实例的析构函数被调用。
警告: 当需要分配共享所有权语义的额外数据时,构造 shared_ptr
可能会抛出 bad_alloc
异常。如果构造函数传递了常规指针,则它假定拥有指向的对象,并在抛出异常时调用删除器。这意味着如果 shared_ptr<T>
的分配失败,shared_ptr<T>(new T(args))
将不会泄漏 T
对象。但是,建议使用 make_shared<T>(args)
或 allocate_shared<T>(alloc, args)
,这使得实现能够优化内存分配。
使用 shared_ptr 分配数组([])
Version < C++ 17
不幸的是,没有直接的方法来使用 make_shared<>
分配数组。
可以使用 new
和 std::default_delete
为 shared_ptr<>
创建数组。
例如,要分配 10 个整数的数组,我们可以将代码编写为
shared_ptr<int> sh(new int[10], std::default_delete<int[]>());
此处必须指定 std::default_delete
以确保使用 delete[]
正确清除分配的内存。
如果我们在编译时知道大小,我们可以这样做:
template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
std::shared_ptr<T> operator()const{
auto r = std::make_shared<std::array<T,N>>();
if (!r) return {};
return {r.data(), r};
}
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }
然后 make_shared_array<int[10]>
返回一个指向 10 个整数的 shared_ptr<int>
,所有默认构造。
Version >= C++ 17
使用 C++ 17,shared_ptr
获得了对数组类型的特殊支持 。不再需要显式指定数组删除器,并且可以使用 []
数组索引运算符取消引用共享指针:
std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;
共享指针可以指向它拥有的对象的子对象:
struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);
p2
和 p1
都拥有 Foo
类型的对象,但是 p2
指向它的 int
成员 x
。这意味着如果 p1
超出范围或被重新分配,底层的 Foo
对象仍将存活,确保 p2
不会悬挂。
重要提示: shared_ptr
只知道自己和使用别名构造函数创建的所有其他 shared_ptr
。它不知道任何其他指针,包括通过引用同一 Foo
实例创建的所有其他 shared_ptr
:
Foo *foo = new Foo;
std::shared_ptr<Foo> shared1(foo);
std::shared_ptr<Foo> shared2(foo); // don't do this
shared1.reset(); // this will delete foo, since shared1
// was the only shared_ptr that owned it
shared2->test(); // UNDEFINED BEHAVIOR: shared2's foo has been
// deleted already!!
所有权转让 shared_ptr
默认情况下,shared_ptr
会增加引用计数,并且不会转移所有权。但是,可以使用 std::move
转移所有权:
shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1