探讨智能指针

探讨智能指针

C++中,动态内存的管理需要依靠一对运算符来完成:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,释放与之关联的内存。

但是,动态内存的使用很容易出现问题。

  • 当忘记释放内存时,就会造成内存泄漏;
  • 若上有指针引用内存,我们就提前释放了该内存,就会产生引用了非法内存的指针(悬空指针)。

为了更容易地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。(定义在memory头文件中)

  • shared_ptr 允许多个指针指向同一个对象;
  • unique_ptr 则独占所指向的对象;
  • weak_ptr,是一种弱引用,指向shared_ptr 管理的对象。

shared_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//空智能指针,
shared_ptr<string> p1;
shared_ptr<list<int>> p2;

//使用的时候需要判断,是否为空
if(p1 && p1->empty())
*p1 = "hi";

//更加安全的创建方法是使用make_shared<T>函数
shared_ptr<int> p3 = make_shared<int>(42); //指向值为42的对象
auto p4 = make_shared<string>(10,'9'); //指向"9999999999"字符串
auto p5 = make_shared<int>(); //值为0

//拷贝构造
auto p6(p5);

//赋值运算符
p3 = p6;

image-20220415222039697

需要特别注意的是,当调用拷贝构造函数后,引用计数递增。相当于有两个指针指向对象;

当进行赋值运算时,如p=q,q的引用计数递增,p的引用计数递减。两个指针都指向q的所指对象。

shared_ptr自动销毁所管理的对象 当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过析构函数完成销毁工作的。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。

1
2
3
4
5
6
7
8
9
10
//p离开作用域,指向的内存会被自动释放掉
void fun(T arg){
shared_ptr<Foo> p = factory(arg);
}

//但是p作为返回值时,内存不会被立刻释放掉
void fun(T arg){
shared_ptr<Foo> p = factory(arg);
return p;//当返回p时,引用计数进行了递增操作
}//p离开了作用域,但它指向的内存不会被释放掉

shared_ptr还会自动释放相关联的内存 当动态对象不再被使用时,shared_ptr类还会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

程序使用动态内存的原因: (1)程序不知道自己需要使用多少对象 (2)程序不知道所需对象的准确类型 (3)程序需要在多个对象间共享数据

定义和改变shared_ptr的其他方法

image-20220415225733681

image-20220415225811513

unique_ptr

一个unique_ptr“拥有”它所指向的对象,某个时刻只能有一个unique_ptr指向一个给定的对象。没有类似make_shared的函数,所以定义unique_ptr的时候要绑定到一个new返回的指针上。

1
unique_ptr<int> p1(new int(42));

unique_ptr不支持拷贝、赋值操作

image-20220415230616073

unique_ptr所有权的转移

1
2
3
4
5
6
//所有权从p1转移给p2
unique_ptr<string> p2(p1.release());

//将p3所有权转移给p2
unique_ptr<string> p3(new string("Tom"));
p2.reset(p3.release()); //reset释放了p2原来指向的内存

不能拷贝unique_ptr有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr.最常见的例子是从函数返回一个unique_ptr.

1
2
3
4
5
unique_ptr<int> clone(int p)
{
//正确:从int*创建一个unique_ptr<int>
return unique_ptr<int>(new int(p));
}

还可以返回一个局部对象的拷贝:

1
2
3
4
5
unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int(p));
return ret;
}

weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变引用计数。

主要是用于解决shared_ptr循环引用的问题。

image-20220415231814811

1
2
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);

由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock,此函数检查weak_ptr指向的对象是否存在。如果存在,lock返回一个指向共享对象的shared_ptr,如果不存在,lock将返回一个空指针。

面试官曾经问过。如果shared_ptr都已经释放掉了,那么weak_ptr还能够访问哪个对象吗?

将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。在实际访问中,可以根据lock的返回值来判断对象是否还存在。

shared_ptr可以直接赋值给一个weak_ptr,那么如何使用weak_ptr初始化一个shared_ptr呢?

使用lock函数

1
2
3
shared_ptr<int> ptr = make_shared<int>(10);  
weak_ptr<int> ptr1 = ptr; // 必须使用shared_ptr初始化weak_ptr
shared_ptr<int> ptr2 = ptr1.lock(); // 利用返回的shared_ptr初始化

其他问题

参考资料

[1]《C++ Primer中文版 第五版》

[2] https://blog.csdn.net/flowing_wind/article/details/81301001


探讨智能指针
https://wuhlan3.gitee.io/2022/04/17/探讨一下智能指针/
Author
Wuhlan3
Posted on
April 17, 2022
Licensed under