共享所有权(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<> 分配数组。

可以使用 newstd::default_deleteshared_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);

p2p1 都拥有 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