December 16, 2021

智能指针

智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。

普通的指针

C++ 中的普通指针来体现:

1
2
3
Demo *demo = new Demo();
demo -> crash();
delete demo;

这里面会有几个问题:

  1. 很多时候,会忘记释放指针,容易导致内存泄漏
  2. 如果crash() 函数抛出异常了,那么demo指向的对象依然不会被安全删除

智能指针OC中的应用

智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命周期

oc中的指针只要是retain属性就默是智能指针,通过引用计数器来实现指针指向的对象的共享。最终控制指针指向的内存的释放。

在oc中基本上所有的对象都是retain(或者strong)属性,除了delegate对象(为了防止循环引用)。

1
2
3
4
5
@interface Book : NSObject {
NSString *name;
NSString *price;
Account *account;
}

在oc的对象中,成员都是指针,对于name,price属性,类对象维护各自那份内存,对于account 属性,有时候会有多个对象共享一份account。

1
2
3
4
5
6
7
8
{
Book *book1 =[ [Book alloc] init];
book1.name = @"book1";
book1.price = @"12.7";
Book *book2 =[ [Book alloc] init];
book2.name = @"book2";
book2.price = @"12.2";
}

book1与book2的price指针并不共享一块内存,各自维护自己的内存。

1
2
3
4
5
6
7
8
{
Account *currAccount = self.account;
Book *book1 = [[Book alloc] init];
book1.account = account;
Book *book2 = [[Book alloc] init];
book2.account = currAccount;
[currAccount release];
}

上述情况,如果没有使用智能指针,则当book1book2共享同一块内存,如果account被释放了,那么book2currAccount就变成了野指针,使用时就会crash;

使用智能指针:

虽然currAccountrelease了,但是currAccountbook1对象保留了一次,所以currAccount并没有被释放,当book1对象释放currAccount的时候,他也不一定被释放,因为book2又保留了它,所以只有当currAccount的引用计数器为0的时候他才会被释放,在oc中,通过设置retain属性就是智能指针的应用。book1book2对象共享了一块内存currAccount。通过引用计数器解决共享内存释放的问题。

智能指针原理

scoped_pty

这是比较简单的一种智能指针,正如其名字所述,scoped_ptr所指向的对象在作用域之外会自动得到析构,scoped_ptr是non-copyable的,也就是说你不能去尝试复制一个scoped_ptr的内容到另外一个scoped_ptr中,这也是为了防止错误的多次析构同一个指针所指向的对象。

shared_pty

引用计数器的指针范围,(和java的GC是一样的逻辑?)

shared_ptr中所实现的本质是引用计数(reference counting),也就是说shared_ptr是支持复制的,复制一个shared_ptr的本质是对这个智能指针的引用次数加1,而当这个智能指针的引用次数降低到0的时候,该对象自动被析构。

如果shared_ptr所表征的引用关系中出现一个环,那么环上所述对象的引用次数都肯定不可能减为0那么也就不会被删除,为了解决这个问题引入了weak_ptr。

weak_pty

weak_ptrshared_ptr的最大区别在于weak_ptr在指向一个对象的时候不会增加其引用计数,因此可以用weak_ptr去指向一个对象并且在weak_ptr仍然指向这个对象的时候析构它,此时你再访问weak_ptr的时候,weak_ptr其实返回的会是一个空的shared_ptr

实际上,通常shared_ptr内部实现的时候维护的就不是一个引用计数,而是两个引用计数,一个表示strong reference,也就是用shared_ptr进行复制的时候进行的计数,一个是weak reference,也就是用weak_ptr进行复制的时候的计数。weak_ptr本身并不会增加strong reference的值,而strong reference降低到0,对象被自动析构。

为什么要采取weak_ptr来解决刚才所述的环状引用的问题呢?需要注意的是环状引用的本质矛盾是不能通过任何程序设计语言的方式来打破的,为了解决环状引用,第一步首先得打破环,也就是得告诉C++,这个环上哪一个引用是最弱的,是可以被打破的,因此在一个环上只要把原来的某一个shared_ptr改成weak_ptr,实质上这个环就可以被打破了,原有的环状引用带来的无法析构的问题也就随之得到了解决。

About this Post

Tagged with #iOS#指针 .

This post is written by Aaron Liu, licensed under CC BY-NC 4.0.