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_shared
:std::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<int> p = std::make_shared<int>(10);
std::cout << *p << std::endl; // 输出:10
*p = 20;
std::cout << *p << std::endl; // 输出:20
2.3 拷贝和赋值
shared_ptr
支持拷贝和赋值操作。多个 shared_ptr
可以共享同一个对象的所有权,且引用计数会相应增加。
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = p1; // p2 和 p1 共享同一个对象
std::cout << *p1 << " " << *p2 << std::endl; // 输出:10 10
- 这种拷贝操作不会产生新的对象,而是增加了对同一对象的引用计数。多个
shared_ptr
实例可能同时存在,它们共同管理相同的对象。
2.4 use_count()
方法
shared_ptr
通过引用计数来管理对象的生命周期,use_count()
方法可以返回当前有多少个 shared_ptr
实例共享同一个对象。
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::cout << p1.use_count() << std::endl; // 输出:1
std::shared_ptr<int> p2 = p1;
std::cout << p1.use_count() << std::endl; // 输出:2
2.5 自动释放内存
当所有指向某个对象的 shared_ptr
都被销毁或重新指向其他对象时,引用计数变为零,shared_ptr
会自动释放管理的对象。
{
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::cout << "Before end of scope, use_count: " << p1.use_count() << std::endl; // 输出:1
} // p1 超出作用域,自动释放内存
std::cout << "After end of scope" << std::endl; // 对象已自动销毁
2.6 reset()
方法
reset()
方法可以将 shared_ptr
的指针设置为空(释放对当前对象的管理),并减少引用计数。
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::cout << p1.use_count() << std::endl; // 输出:1
p1.reset(); // 释放内存
std::cout << p1.use_count() << std::endl; // 输出:0
2.7 get()
方法
get()
方法返回 shared_ptr
中管理的原始指针。如果你需要直接使用原始指针,可以使用 get()
,但要注意不要手动删除它,因为 shared_ptr
会负责对象的销毁。
std::shared_ptr<int> p1 = std::make_shared<int>(10);
int* raw_ptr = p1.get();
std::cout << *raw_ptr << std::endl; // 输出:10
3. 循环引用问题与 shared_ptr
一个常见的错误是 循环引用,这会导致内存泄漏。循环引用发生在两个或多个对象之间彼此持有 shared_ptr
,从而导致它们的引用计数永远不会归零。
struct A;
struct B;
struct A {
std::shared_ptr<B> b_ptr;
};
struct B {
std::shared_ptr<A> a_ptr;
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<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<B> b_ptr;
};
struct B {
std::shared_ptr<A> a_ptr;
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 此时没有循环引用,weak_ptr 不增加引用计数
}
std::weak_ptr
可以从 shared_ptr
转换回来,但它不会保持对象的生命周期。使用 std::weak_ptr
可以避免循环引用的问题。
4. shared_ptr
的应用场景
shared_ptr
适用于以下情况:
- 多个对象共享所有权:多个不同的部分(线程、模块等)都需要共享对某个对象的所有权时,
shared_ptr
很适用。例如,多个线程需要访问同一个对象,并且我们希望在最后一个线程不再使用该对象时自动释放资源。 - 复杂数据结构:例如图、树等结构,其中每个节点可能被多个父节点共享,
shared_ptr
可以避免手动管理内存。 - 资源管理:
shared_ptr
可以管理动态分配的资源,确保在资源不再使用时释放资源。
5. 总结
shared_ptr
是 C++ 标准库中的智能指针,主要用于管理动态分配的内存,并通过引用计数来避免内存泄漏。shared_ptr
可以轻松地通过std::make_shared
创建,支持拷贝、赋值等操作,并且能够自动释放内存。- 需要注意避免循环引用,若有循环引用的问题,可以使用
std::weak_ptr
来打破循环依赖。 shared_ptr
在需要多个组件共享对资源的所有权时非常有用,是资源管理和内存管理的强大工具。
通过合理使用 shared_ptr
,你可以显著降低内存泄漏的风险,同时简化资源管理逻辑。
发表回复