shared_ptr 的用法详解

在 C++ 中,shared_ptr 是一种智能指针,属于 C++11 标准 引入的 std::shared_ptr,用于管理动态分配的对象,并确保在对象不再被需要时自动释放内存。它是 std::unique_ptr 的另一种智能指针,具有引用计数的特性,能够在多个智能指针实例之间共享对同一对象的所有权。

1. shared_ptr 的基本概念

shared_ptr 是 C++ 标准库中的一个模板类,它提供了对动态分配内存的自动管理功能。它的核心特性是引用计数。每次有一个新的 shared_ptr 实例指向同一对象时,引用计数会增加;当一个 shared_ptr 被销毁或指向另一个对象时,引用计数会减少。当引用计数变为 0 时,shared_ptr 会自动释放管理的内存。

2. 创建和使用 shared_ptr

2.1 构造 shared_ptr

可以通过几种方式创建 shared_ptr

  • 使用 std::make_sharedstd::make_shared 是推荐的方式,它通过构造函数一次性创建对象并返回 shared_ptr,避免了不必要的内存分配,同时可以避免手动指定类型。std::shared_ptr<int> p1 = std::make_shared<int>(10); // 创建一个指向整数10的shared_ptr
  • 使用 new 操作符:也可以显式使用 new 来创建对象并将其传递给 shared_ptr。这种方式会创建一个 shared_ptr 指向由 new创建的对象。std::shared_ptr<int> p2(new int(20)); // 创建一个指向整数20的shared_ptr

2.2 访问和修改对象

通过 shared_ptr,可以像普通指针一样访问和修改对象:

std::shared_ptr&lt;int> p = std::make_shared&lt;int>(10);
std::cout &lt;&lt; *p &lt;&lt; std::endl;  // 输出:10

*p = 20;
std::cout &lt;&lt; *p &lt;&lt; std::endl;  // 输出:20

2.3 拷贝和赋值

shared_ptr 支持拷贝和赋值操作。多个 shared_ptr 可以共享同一个对象的所有权,且引用计数会相应增加。

std::shared_ptr&lt;int> p1 = std::make_shared&lt;int>(10);
std::shared_ptr&lt;int> p2 = p1;  // p2 和 p1 共享同一个对象
std::cout &lt;&lt; *p1 &lt;&lt; " " &lt;&lt; *p2 &lt;&lt; std::endl;  // 输出:10 10

  • 这种拷贝操作不会产生新的对象,而是增加了对同一对象的引用计数。多个 shared_ptr 实例可能同时存在,它们共同管理相同的对象。

2.4 use_count() 方法

shared_ptr 通过引用计数来管理对象的生命周期,use_count() 方法可以返回当前有多少个 shared_ptr 实例共享同一个对象。

std::shared_ptr&lt;int> p1 = std::make_shared&lt;int>(10);
std::cout &lt;&lt; p1.use_count() &lt;&lt; std::endl;  // 输出:1

std::shared_ptr&lt;int> p2 = p1;
std::cout &lt;&lt; p1.use_count() &lt;&lt; std::endl;  // 输出:2

2.5 自动释放内存

当所有指向某个对象的 shared_ptr 都被销毁或重新指向其他对象时,引用计数变为零,shared_ptr 会自动释放管理的对象。

{
    std::shared_ptr&lt;int> p1 = std::make_shared&lt;int>(10);
    std::cout &lt;&lt; "Before end of scope, use_count: " &lt;&lt; p1.use_count() &lt;&lt; std::endl;  // 输出:1
}  // p1 超出作用域,自动释放内存

std::cout &lt;&lt; "After end of scope" &lt;&lt; std::endl;  // 对象已自动销毁

2.6 reset() 方法

reset() 方法可以将 shared_ptr 的指针设置为空(释放对当前对象的管理),并减少引用计数。

std::shared_ptr&lt;int> p1 = std::make_shared&lt;int>(10);
std::cout &lt;&lt; p1.use_count() &lt;&lt; std::endl;  // 输出:1
p1.reset();  // 释放内存
std::cout &lt;&lt; p1.use_count() &lt;&lt; std::endl;  // 输出:0

2.7 get() 方法

get() 方法返回 shared_ptr 中管理的原始指针。如果你需要直接使用原始指针,可以使用 get(),但要注意不要手动删除它,因为 shared_ptr 会负责对象的销毁。

std::shared_ptr&lt;int> p1 = std::make_shared&lt;int>(10);
int* raw_ptr = p1.get();
std::cout &lt;&lt; *raw_ptr &lt;&lt; std::endl;  // 输出:10


3. 循环引用问题与 shared_ptr

一个常见的错误是 循环引用,这会导致内存泄漏。循环引用发生在两个或多个对象之间彼此持有 shared_ptr,从而导致它们的引用计数永远不会归零。

struct A;
struct B;

struct A {
    std::shared_ptr&lt;B> b_ptr;
};

struct B {
    std::shared_ptr&lt;A> a_ptr;
};

int main() {
    std::shared_ptr&lt;A> a = std::make_shared&lt;A>();
    std::shared_ptr&lt;B> b = std::make_shared&lt;B>();
    
    a->b_ptr = b;
    b->a_ptr = a;

    // 这里 a 和 b 会互相持有对方的 shared_ptr,引用计数永远不会为 0,导致内存泄漏
}

解决方法:使用 std::weak_ptr

为了避免循环引用,可以使用 std::weak_ptr,它不增加引用计数。weak_ptr 仅用于观察对象,不会阻止对象的销毁。

struct A;
struct B;

struct A {
    std::weak_ptr&lt;B> b_ptr;
};

struct B {
    std::shared_ptr&lt;A> a_ptr;
};

int main() {
    std::shared_ptr&lt;A> a = std::make_shared&lt;A>();
    std::shared_ptr&lt;B> b = std::make_shared&lt;B>();
    
    a->b_ptr = b;
    b->a_ptr = a;
    
    // 此时没有循环引用,weak_ptr 不增加引用计数
}

std::weak_ptr 可以从 shared_ptr 转换回来,但它不会保持对象的生命周期。使用 std::weak_ptr 可以避免循环引用的问题。


4. shared_ptr 的应用场景

shared_ptr 适用于以下情况:

  1. 多个对象共享所有权:多个不同的部分(线程、模块等)都需要共享对某个对象的所有权时,shared_ptr 很适用。例如,多个线程需要访问同一个对象,并且我们希望在最后一个线程不再使用该对象时自动释放资源。
  2. 复杂数据结构:例如图、树等结构,其中每个节点可能被多个父节点共享,shared_ptr 可以避免手动管理内存。
  3. 资源管理shared_ptr 可以管理动态分配的资源,确保在资源不再使用时释放资源。

5. 总结

  • shared_ptr 是 C++ 标准库中的智能指针,主要用于管理动态分配的内存,并通过引用计数来避免内存泄漏。
  • shared_ptr 可以轻松地通过 std::make_shared 创建,支持拷贝、赋值等操作,并且能够自动释放内存。
  • 需要注意避免循环引用,若有循环引用的问题,可以使用 std::weak_ptr 来打破循环依赖。
  • shared_ptr 在需要多个组件共享对资源的所有权时非常有用,是资源管理和内存管理的强大工具。

通过合理使用 shared_ptr,你可以显著降低内存泄漏的风险,同时简化资源管理逻辑。