C++ 11 智能指针——shared_ptr类成员函数详解

C++ 11 模板库的 <memory> 头文件中定义的智能指针,即 shared_ptr 模板类,用来管理指针的存储,提供有限的内存回收函数,可同时与其他对象共享该管理功能,从而帮助彻底消除内存泄漏和悬空指针的问题。

shared_ptr 类型的对象能够获得指针的所有权并共享该所有权:一旦他们获得所有权,指针的所有者组就会在最后一个释放该所有权时负责删除该指针。

shared_ptr 对象一旦它们自己被销毁,或者它们的值因赋值操作或显式调用 shared_ptr::reset 而改变时,就会释放它们共同拥有的对象的所有权。一旦通过指针共享所有权的所有 shared_ptr 对象都释放了该所有权,则删除托管对象(通常通过调用 ::delete,也可以在构造时指定不同的删除器)。

  1. 同一个shared_ptr被多个线程读,是线程安全的;
  2. 同一个shared_ptr被多个线程写,不是线程安全的;
  3. 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。

shared_ptr 对象只能通过复制它们的值来共享所有权:如果两个 shared_ptr 是从同一个(非shared_ptr)指针构造(或制造)的,它们都将拥有该指针而不共享它,当其中一个释放时会导致潜在的访问问题它(删除其托管对象)并将另一个指向无效位置。

此外,shared_ptr 对象可以共享一个指针的所有权,同时指向另一个对象。这种能力被称为别名(参见构造函数),通常用于在拥有成员对象时指向成员对象。因此,一个 shared_ptr 可能与两个指针相关:

1)一个存储的指针,即它所指向的指针,以及它用 operator* 取消引用的指针。

2)一个所有者的指针(可能是共享的),它是所有权组负责在某个时间点删除的指针,并计为使用。

通常,存储指针和所有者指针指向同一个对象,但别名 shared_ptr 对象(使用别名构造函数及其副本构造的对象)可能指向不同的对象。不拥有任何指针的 shared_ptr 称为null shared_ptr。不指向任何对象的 shared_ptr 称为null shared_ptr 并且不应取消引用。请注意,空的 shared_ptr 不一定是null shared_ptr,null shared_ptr 也不一定是空的 shared_ptr。shared_ptr 对象通过提供对它们通过运算符 * 和 -> 指向的对象的访问来复制有限的指针功能。出于安全原因,它们不支持指针算术。类似weak_ptr,能够与 shared_ptr 对象共享指针,而无需拥有它们。shared_ptr 有以下成员函数:

(1)构造函数

shared_ptr的构造函数根据使用的参数类型构造 shared_ptr 对象:

1) 默认构造函数:constexpr shared_ptr() noexcept;

2) 从空指针构造:constexpr shared_ptr(nullptr_t) : shared_ptr() {}

3) 从指针构造:template <class U> explicit shared_ptr (U* p);

4) 从指针 + 删除器构造:template <class U, class D> shared_ptr (U* p, D del); template <class D> shared_ptr (nullptr_t p, D del);

5) 从指针 + 删除器 + 分配器构造:template <class U, class D, class Alloc> shared_ptr (U* p, D del, Alloc alloc); template <class D, class Alloc> shared_ptr (nullptr_t p, D del, Alloc alloc);

6) 复制构造函数:shared_ptr (const shared_ptr& x) noexcept; template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;

7) 从weak_ptr 复制:template <class U> explicit shared_ptr (const weak_ptr<U>& x);

8) 移动构造函数:shared_ptr (shared_ptr&& x) noexcept; template <class U> shared_ptr (shared_ptr<U>&& x) noexcept;

9) 从其他类型的托管指针移动:template <class U> shared_ptr (auto_ptr<U>&& x); template <class U, class D> shared_ptr (unique_ptr<U,D>&& x);

10) 别名构造函数:template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;

默认构造函数 1) 和 2)对象为空(不拥有指针,使用计数为零)。从指针构造3)该对象拥有 p,将使用计数设置为 1。从指针 + 删除器构造 4)与 3) 相同,但该对象还拥有删除器 del 的所有权(并在某些时候需要删除 p 时使用它)。从指针 + 删除器 + 分配器构造 5)与 4) 相同,但内部使用所需的任何内存都是使用 alloc 分配的(对象保留一份副本,但不取得所有权)。复制构造函数 6)如果 x 不为空,则对象共享 x 资产的所有权并增加使用次数。如果 x 为空,则构造一个空对象(如同默认构造)。从weak_ptr 7) 复制同上6),除了如果 x 已经过期,则抛出 bad_weak_ptr 异常。移动构造函数 8)该对象获取由 x 管理的内容,包括其拥有的指针。 x 变成一个空对象(就像默认构造的一样)。从其他类型的托管指针移动 9)对象获取由 x 管理的内容并将使用计数设置为 1。放弃的对象变为空,自动失去指针的所有权。别名构造函数 10)同6),除了存储的指针是p。该对象不拥有 p,也不会管理其存储。相反,它共同拥有 x 的托管对象并算作 x 的一种额外使用。它还将在发布时删除 x 的指针(而不是 p)。它可以用来指向已经被管理的对象的成员。

1) p: 其所有权被对象接管的指针。此指针值不应已由任何其他托管指针管理(即,此值不应来自托管指针上的调用成员 get)。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。

2) del: 用于释放拥有的对象的删除器对象。这应该是一个可调用对象,将指向 T 的指针作为其函数调用的参数(其中 T 是 shared_ptr 的模板参数)。

3) alloc:用于分配/取消分配内部存储的分配器对象。

4) X: 托管指针类型的对象。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。

#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
  std::shared_ptr<int> p1;
  std::shared_ptr<int> p2 (nullptr);
  std::shared_ptr<int> p3 (new int);
  std::shared_ptr<int> p4 (new int, std::default_delete<int>());
  std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
  std::shared_ptr<int> p6 (p5);
  std::shared_ptr<int> p7 (std::move(p6));
  std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
  std::shared_ptr<C> obj (new C);
  std::shared_ptr<int> p9 (obj, obj->data);

  std::cout << "use_count:\n";
  std::cout << "p1: " << p1.use_count() << '\n';
  std::cout << "p2: " << p2.use_count() << '\n';
  std::cout << "p3: " << p3.use_count() << '\n';
  std::cout << "p4: " << p4.use_count() << '\n';
  std::cout << "p5: " << p5.use_count() << '\n';
  std::cout << "p6: " << p6.use_count() << '\n';
  std::cout << "p7: " << p7.use_count() << '\n';
  std::cout << "p8: " << p8.use_count() << '\n';
  std::cout << "p9: " << p9.use_count() << '\n';
  return 0;
}

(2)析构函数

析构函数的作用是销毁shared_ptr对象。 但是,在此之前,根据成员 use_count 的值,它可能会产生以下副作用:

1)如果 use_count 大于 1(即该对象与其他 shared_ptr 对象共享其托管对象的所有权):与其共享所有权的其他对象的使用计数减 1。
2)如果 use_count 为 1(即对象是托管指针的唯一所有者):删除其拥有指针所指向的对象(如果 shared_ptr 对象是用特定的删除器构造的,则调用此函数;否则,函数使用运算符 删除)。
3)如果 use_count 为零(即对象为空),则该析构函数没有副作用。

用法举例:

#include <iostream>
#include <memory>

int main() {
    auto deleter = [](int* p) {
        std::cout << "[deleter called]\n"; delete p;
    };

    std::shared_ptr<int> foo(new int, deleter);

    std::cout << "use_count: " << foo.use_count() << '\n';

    return 0;                        // [deleter called]
}

(3)赋值运算“=”

1) 复制:shared_ptr& operator= (const shared_ptr& x) noexcept; template <class U> shared_ptr& operator= (const shared_ptr<U>& x) noexcept;

2) 移动:shared_ptr& operator= (shared_ptr&& x) noexcept; template <class U> shared_ptr& operator= (shared_ptr<U>&& x) noexcept;

3) 从...移动:template <class U> shared_ptr& operator= (auto_ptr<U>&& x); template <class U, class D> shared_ptr& operator= (unique_ptr<U,D>&& x);

复制分配1) 将对象添加为 x 资产的共享所有者,从而增加它们的 use_count。移动分配 2) 将所有权从 x 转移到 shared_ptr 对象而不改变 use_count。 x 变成一个空的 shared_ptr(就像默认构造的一样)。同样,来自其他托管指针类型 3) 的移动分配也会转移所有权,并使用 set a use count of 1 进行初始化。

此外,在上述所有情况下,对该函数的调用与在其值更改之前调用了 shared_ptr 的析构函数具有相同的副作用(如果此 shared_ptr 是唯一的,则包括删除托管对象)。不能将指针的值直接分配给 shared_ptr 对象。您可以改用 make_shared 或成员重置。

托管指针类型的对象。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。

用法举例:

#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo;
  std::shared_ptr<int> bar (new int(10));

  foo = bar;                          // copy

  bar = std::make_shared<int> (20);   // move

  std::unique_ptr<int> unique (new int(30));
  foo = std::move(unique);            // move from unique_ptr

  std::cout << "*foo: " << *foo << '\n';
  std::cout << "*bar: " << *bar << '\n';

  return 0;
}

(4)swap函数

函数声明:void swap (shared_ptr& x) noexcept; 参数x: 另一个相同类型的 shared_ptr 对象(即,具有相同的类模板参数 T)。作用是将 shared_ptr 对象的内容与 x 的内容交换,在它们之间转移任何托管对象的所有权,而不会破坏或改变两者的使用计数。

用法举例:

#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo (new int(10));
  std::shared_ptr<int> bar (new int(20));

  foo.swap(bar);

  std::cout << "*foo: " << *foo << '\n';
  std::cout << "*bar: " << *bar << '\n';

  return 0;
}

(5)reset函数

重置shared_ptr,对于声明1) 对象变为空(如同默认构造)。在所有其他情况下,shared_ptr 以使用计数为 1 获取 p 的所有权,并且 - 可选地 - 使用 del 和/或 alloc 作为删除器 和分配器。另外,调用这个函数有同样的副作用,就像在它的值改变之前调用了shared_ptr 的析构函数一样(包括删除托管对象,如果这个shared_ptr 是唯一的)。

1) void reset() noexcept;
2) template <class U> void reset (U* p);
3) template <class U, class D> void reset (U* p, D del);
4) template <class U, class D, class Alloc> void reset (U* p, D del, Alloc alloc);

用法举例:

#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp;  // empty

  sp.reset (new int);       // takes ownership of pointer
  *sp=10;
  std::cout << *sp << '\n';

  sp.reset (new int);       // deletes managed object, acquires new pointer
  *sp=20;
  std::cout << *sp << '\n';

  sp.reset();               // deletes managed object

  return 0;
}

(6)get函数

函数声明:element_type* get() const noexcept; get()返回存储的指针。存储的指针指向shared_ptr对象解引用的对象,一般与其拥有的指针相同。存储的指针(即这个函数返回的指针)可能不是拥有的指针(即对象销毁时删除的指针)如果 shared_ptr 对象是别名(即,别名构造的对象及其副本)。

用法举例:

#include <iostream>
#include <memory>

int main () {
  int* p = new int (10);
  std::shared_ptr<int> a (p);

  if (a.get()==p)
    std::cout << "a and p point to the same location\n";

  // three ways of accessing the same address:
  std::cout << *a.get() << "\n";
  std::cout << *a << "\n";
  std::cout << *p << "\n";

  return 0;
}

(7)取对象运算“*”

函数声明:element_type& operator*() const noexcept; 取消引用对象。返回对存储指针指向的对象的引用。等价于:*get()。如果shared_ptr的模板参数为void,则该成员函数是否定义取决于平台和编译器,以及它的返回类型 在这种情况下。

用法举例:

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> foo(new int);
    std::shared_ptr<int> bar(new int(100));

    *foo = *bar * 2;

    std::cout << "foo: " << *foo << '\n';
    std::cout << "bar: " << *bar << '\n';

    return 0;
}

(8)“->”操作符

函数声明:element_type* operator->() const noexcept; 取消引用对象成员。返回一个指向存储指针指向的对象的指针,以便访问其成员之一。如果存储的指针是空指针,则不应调用该成员函数,它返回与 get() 相同的值。

用法举例:

#include <iostream>
#include <memory>

struct C { int a; int b; };

int main() {
    std::shared_ptr<C> foo;
    std::shared_ptr<C> bar(new C);

    foo = bar;

    foo->a = 10;
    bar->b = 20;

    if (foo) std::cout << "foo: " << foo->a << ' ' << foo->b << '\n';
    if (bar) std::cout << "bar: " << bar->a << ' ' << bar->b << '\n';

    return 0;
}

(9)use_count函数

函数声明:long int use_count() const noexcept; use_count 返回与此对象(包括它)在同一指针上共享所有权的 shared_ptr 对象的数量。如果这是一个空的 shared_ptr,则该函数返回零。库实现不需要保留任何特定所有者集的计数 ,因此调用此函数可能效率不高。 要具体检查 use_count 是否为 1,也可以使用 member unique 代替,这样可能更快。

(10)unique函数

函数声明:bool unique() const noexcept; 检查是否唯一 返回 shared_ptr 对象是否不与其他 shared_ptr 对象共享其指针的所有权(即,它是唯一的)。 空指针从来都不是唯一的(因为它们不拥有任何指针)。 如果唯一的 shared_ptr 对象释放此所有权,则它们负责删除其托管对象(请参阅析构函数)。 此函数应返回与 (use_count()==1) 相同的值,尽管它可能以更有效的方式执行此操作。 如果这是唯一的 shared_ptr,则返回值 true,否则返回 false。

用法举例:

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> foo;
    std::shared_ptr<int> bar(new int);

    std::cout << "foo unique?\n" << std::boolalpha;

    std::cout << "1: " << foo.unique() << '\n';  // false (empty)

    foo = bar;
    std::cout << "2: " << foo.unique() << '\n';  // false (shared with bar)

    bar = nullptr;
    std::cout << "3: " << foo.unique() << '\n';  // true

    return 0;
}

(11)“bool”操作

函数声明:explicit operator bool() const noexcept; 检查是否为 null。 返回存储的指针是否为空指针。 存储的指针指向 shared_ptr 对象解除引用的对象,通常与其拥有的指针相同(销毁时删除的指针)。 如果 shared_ptr 对象是别名(即别名构造的对象及其副本),它们可能会有所不同。该函数返回的结果与 get()!=0 相同。 请注意,空的 shared_ptr(即此函数返回 false 的指针)不一定是空的 shared_ptr。 别名可能拥有某个指针但指向空,或者所有者组甚至可能拥有空指针(参见构造函数 4 和 5)。

用法举例:

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> foo;
    std::shared_ptr<int> bar(new int(34));

    if (foo) std::cout << "foo points to " << *foo << '\n';
    else std::cout << "foo is null\n";

    if (bar) std::cout << "bar points to " << *bar << '\n';
    else std::cout << "bar is null\n";

    return 0;
}

(12)owner_before函数

函数声明:

template <class U> bool owner_before (const shared_ptr<U>& x) const;
template <class U> bool owner_before (const weak_ptr<U>& x) const;

基于所有者的排序。根据严格的弱基于所有者的顺序返回是否认为对象在 x 之前。与 operator< 重载不同,此排序考虑了 shared_ptr 的拥有指针,而不是存储的指针,使得两个 如果它们都共享所有权,或者它们都为空,即使它们存储的指针值不同,这些对象中的一个被认为是等效的(即,无论操作数的顺序如何,该函数都返回 false)。 如果 shared_ptr 对象是一个别名(别名构造的对象及其副本),则 shared_ptr 对象解引用)可能不是拥有的指针(即对象销毁时删除的指针)。该函数由 owner_less 调用以确定其结果。

用法举例:

#include <iostream>
#include <memory>

int main() {
    int* p = new int(10);

    std::shared_ptr<int> a(new int(20));
    std::shared_ptr<int> b(a, p);  // alias constructor

    std::cout << "comparing a and b...\n" << std::boolalpha;
    std::cout << "value-based: " << (!(a < b) && !(b < a)) << '\n';
    std::cout << "owner-based: " << (!a.owner_before(b) && !b.owner_before(a)) << '\n';

    delete p;
    return 0;
}

posted @ 2021-07-19 18:46  Jcpeng_std  阅读(5857)  评论(0编辑  收藏  举报