三种智能指针的使用场景shared_ptr、unique_ptr和weak_ptr

三种智能指针的使用场景shared_ptr、unique_ptr和weak_ptr

C++11引入了三种智能指针:shared_ptr,unique_ptr和weak_ptr,它们用于解决原生指针可能导致的内存泄露和非法引用问题。下面我们分别看一下这三种智能指针的使用场景:

  1. shared_ptr:shared_ptr是一种引用计数的智能指针,当你需要在多个地方共享同一个资源时,可以使用它。它会跟踪引用到某个对象的shared_ptr数量。当最后一个shared_ptr离开其作用范围或者被赋予新的值时,它所指向的对象就会被自动删除。在循环引用的情况下,需要配合使用weak_ptr避免内存泄露。
  2. unique_ptr:unique_ptr是一种具有严格所有权语义的智能指针,它不允许多个指针指向同一对象。因此,unique_ptr适合在作用域内拥有对象的独占所有权,并且在作用域结束时需要自动释放该对象。例如,它可以用于防止资源泄漏(如打开的文件或分配的内存)。
  3. weak_ptr:weak_ptr是为了配合shared_ptr而存在的,它可以从一个shared_ptr或者另一个weak_ptr对象构造,其目的是提供对管理对象的访问,但是它并不改变引用计数。你可以把weak_ptr看作是shared_ptr的一个安全版本,用于解决shared_ptr可能存在的循环引用问题。当其所指向的对象被释放后,尝试通过weak_ptr访问对象将抛出一个std::bad_weak_ptr异常。

在使用智能指针时,应尽量优先选择unique_ptr,如果你确实需要多个所有者,那么应选择shared_ptr。在处理shared_ptr可能产生的循环引用问题时,应使用weak_ptr。

在深入探讨这些智能指针的使用场景之前,我们需要理解它们的设计原则以及如何正确地使用它们。

unique_ptr

unique_ptr的主要用途是管理一个对象的生命周期。当unique_ptr被销毁时,它会自动销毁(或释放)其所有权的对象。这可以确保在任何情况下,包括异常,都可以正确释放资源。

例如,你可能会在一个函数中创建一个动态数组,并希望这个数组在函数返回或出现异常时被正确地删除。在这种情况下,你可以使用unique_ptr来管理这个数组。

unique_ptr示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <memory>

void process(std::unique_ptr<int> p)
{
std::cout << "Inside function: " << *p << std::endl;
}

int main()
{
std::unique_ptr<int> p1(new int(10));
process(std::move(p1)); // 移动unique_ptr,现在p1不再拥有内存

if (p1)
{
std::cout << "p1 owns the memory\n";
}
else
{
std::cout << "p1 doesn't own the memory\n";
}

return 0;
}

shared_ptr

shared_ptr的主要用途是在多个对象之间共享所有权。shared_ptr使用引用计数来跟踪有多少个shared_ptr对象共享同一个资源。当最后一个shared_ptr被销毁时,其管理的资源也会被自动释放。

例如,如果你有一个需要在多个线程之间共享的大对象,并且你希望当最后一个需要它的线程完成时删除这个对象,你就可以使用shared_ptr。

shared_ptr示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <memory>

int main()
{
std::shared_ptr<int> p1(new int(10));
std::cout << "p1 Reference Count: " << p1.use_count() << std::endl;

std::shared_ptr<int> p2 = p1; // 这里拷贝了 shared_ptr,计数增加
std::cout << "p2 Reference Count: " << p2.use_count() << std::endl;
std::cout << "p1 Reference Count: " << p1.use_count() << std::endl;

return 0;
}

  • 如何理解shared_ptr是在多个对象之间共享所有权

shared_ptr是一种智能指针,它允许多个指针引用同一个对象。这是通过引用计数实现的,即每个shared_ptr都会跟踪有多少个shared_ptr实例引用同一个对象。当一个shared_ptr实例被销毁或者重新指向其他对象时,原来对象的引用计数减1,当引用计数减为0时,对象将被自动删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <memory>

int main()
{
std::shared_ptr<int> sp1(new int(10));
std::cout << "sp1's value: " << *sp1 << std::endl;
std::cout << "Use count: " << sp1.use_count() << std::endl;

std::shared_ptr<int> sp2 = sp1; // 复制shared_ptr,sp1和sp2现在指向同一个对象,引用计数增加
std::cout << "sp2's value: " << *sp2 << std::endl;
std::cout << "Use count: " << sp2.use_count() << std::endl; // use_count现在应为2,因为有两个shared_ptr指向同一个对象

sp1.reset(); // sp1不再指向对象,引用计数减1
std::cout << "After resetting sp1, use count: " << sp2.use_count() << std::endl; // use_count现在应为1

return 0;
}

在这个示例中,我们创建了两个shared_ptr,sp1和sp2,它们共享同一个动态分配的int对象。通过调用use_count()函数,我们可以看到每个shared_ptr实例销毁或者重新指向其他对象时引用计数的变化。

weak_ptr

weak_ptr的主要用途是避免shared_ptr可能产生的循环引用问题。循环引用问题是指两个或更多的shared_ptr对象互相引用,形成一个循环。这会导致对象无法被正确释放。

例如,如果你有一个类A和一个类B,并且A有一个指向B的shared_ptr,B也有一个指向A的shared_ptr,这就会产生一个循环引用。解决这个问题的方法就是将其中一个shared_ptr改为weak_ptr。这样,当一方不再需要时,可以被正确地释放。

weak_ptr示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <memory>

int main()
{
std::shared_ptr<int> p1(new int(10));
std::cout << "p1 Reference Count: " << p1.use_count() << std::endl;

std::weak_ptr<int> p2 = p1; // 这里创建了weak_ptr,引用计数并没有增加
std::cout << "p1 Reference Count: " << p1.use_count() << std::endl;

if (auto p3 = p2.lock()) // 从 weak_ptr 创建 shared_ptr
{
std::cout << "p1 Reference Count: " << p1.use_count() << std::endl;
}

return 0;
}


三种智能指针的使用场景shared_ptr、unique_ptr和weak_ptr
https://qiangsun89.github.io/2023/05/22/三种智能指针的使用场景shared-ptr、unique-ptr和weak-ptr/
作者
Qiang Sun
发布于
2023年5月22日
许可协议