auto_ptr
这个智能指针已经被C11标准弃用,C98中它的作用是:动态分配对象以及在对象不需要时自动执行清理。
auto_ptr在构造时获取某个对象的所有权,在析构时释放该对象,所以我们不用担心它在何时释放该对象,也不用担心发生异常产生内存泄露。
但要注意:
- 一个萝卜一个坑,两个auto_ptr不能同时拥有同一个对象。
- 不能用auto_ptr来管理数组。
- 构造函数的explicit关键字有效阻止从一个“裸”指针隐式转化为auto_ptr类型。
auto_ptr的拷贝构造和赋值函数强调“所有权的转移”,即智能指针将失去对“裸”指针的一切权力,拷贝和赋值的目标对象也要先释放原来拥有的对象。
注意:
1、一个auto_ptr被拷贝或赋值后,其已经失去了对原对象的所有权,对于这个auto_ptr的提领操作是不安全的。
提领操作:有两个操作,一是返回其所拥有对象的引用,另一个是实现了通过auto_ptr调用其所拥有对象的成员。
一定要避免auto_ptr作为函数参数按值传递,因为在函数调用过程中函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),传入的实参auto_ptr就失去了对原对象的所有权,而该对象会在函数结束时被局部auto_ptr删除,我们可以用const reference的方式来传递auto_ptr。
2、拷贝构造函数和赋值函数都提供了一个成员模板在不覆盖“正统”版本的情况下实现auto_ptr的隐式转换。
3、因为auto_ptr不具有值语义,所以不能被用在STL标准容器中。
“值语义:对于一个具有值语义的变量赋值可以转换成内存的按位拷贝。
辅助函数
- get 用来显式的返回auto_ptr所拥有的对象
- release 用来转移所有权。
- reset 用来接收所有权,如果接收者已拥有某对象,必须先释放原对象。
总结
- auto_ptr不能指向数组
- auto_ptr不能共享所有权
- auto_ptr不能通过复制操作初始化
- auto_ptr不能放入容器中使用
- auto_ptr不能作为容器的成员
C11中用unique_ptr来代替auto_ptr
unique_ptr对象不能进行复制操作只能进行移动操作。在某一时刻只能有一个unique_ptr指向该对象。当它本身被删除或离开其作用域时会自动释放其指向对象所占用的资源。
- 如何创建unique_ptr
创建一个unique_ptr我们需要将一个new操作符返回的指针传递给它的构造函数。
int main()
{
unique_ptr<int> pInt(new int(5));
cout << *pInt;
}
- 无法进行拷贝构造和赋值
int main()
{
unique_ptr<int> pInt(new int(5));
unique_ptr<int> pInt1(pInt);//报错
unique_ptr<int> pInt2;
pInt2 = pInt;//报错
}
- 可以进行移动构造和移动赋值操作
如果要转移其所有权,可以使用std::move()函数。
int main()
{
unique_ptr<int> pInt(new int(5));
unique_ptr<int> pInt1 = std::move(pInt);//转移所有权
cout << *pInt << endl;//error,pInt为空
cout << *pInt1 << endl;
unique_ptr<int> pInt2(std::move(pInt1));
}
- 可以返回unique_ptr
允许函数返回一个unique_ptr。 - 使用场景
1、为动态申请的资源提供异常安全保证,用unique_ptr管理动态内存,只要unique_ptr创建成功,就一定会调用析构函数,确保动态资源被安全释放。
2、返回函数内申请资源的所有权
3、在容器内保存指针
int main()
{
vector<unique_ptr<int> > vec;
unique_ptr<int> pInt(new int (5));
vec.pushback(std::move(pInt));//使用移动语义
}
4、管理动态数组
标准库提供了一个管理动态数组的unique_ptr版本
int main()
{
unique_ptr<int[]> p(new int[5]{1,2,3,4,5});
p[0] = 0;//重载了operator[]
}
share_ptr(共享型智能指针)
share_ptr是一个引用计数型智能指针,用于共享对象的所有权,也就是说它允许多个指针指向同一个对象。允许多个该类型的指针共享同一个分配对象,同时使用”引用计数“方法来管理对象资源,每一个share_ptr对象关联一个共享的引用计数。拷贝和赋值都是使引用计数增加,当给一个share_ptr赋予一个新值或是share_ptr被销毁(例如一个局部的share_ptr离开其作用域)时,引用计数就会递减。
share_ptr的引用计数变成0,它就会自动释放自己所管理的对象。
- 创建share_ptr
最高效的方法是调用make_shared 库函数,该函数会在堆中分配一个对象并初始化,最后返回指向此对象的share_ptr实例。如果不想用这个,也可以先明确new一个对象,然后把其原始指针传递给share_ptr的构造函数。
int main()
{
share_ptr<string> pstr = make_share<string>(10,'y');
cout << *pstr;
int *p = new int(10);
share_ptr<int> pstr1(p);
cout << *pstr1 << endl;
return 0;
}
- 访问所指对象
和普通函数类似,可以用操作符‘*’解引用获得原始对象进而访问各个成员,也可以使用指针访问符‘->’来访问原始对象的各个成员。 - 拷贝和赋值操作
可以用一个share_ptr来初始化另一个share_ptr,这样会增加引用计数值。
如果share_ptr实例p和q的指向类型相同或可以相互转换,我们可以进行如 p = q 的赋值操作,这样会递减p的引用计数值,递增q的引用计数值。 - 检查引用计数
use_count()函数返回当前的引用计数值,但use_count()可能效率很低,应该把它用于测试或调试。
unique()函数用于测试该share_ptr是否是原始指针的唯一拥有者,也就是如果use_count()函数返回值为1时返回true,否则返回false。 - share_ptr的线程安全性
1、引用计数本身是线程安全(引用计数是一个原子操作)。
2、多个线程同时读同一个share_ptr对象是线程安全的。
3、如果多个线程对同一个share_ptr进行读和写则需要加锁。
4、多线程读写share_ptr所指向的同一对象,不管是相同的share_ptr对象,还是不同的share_ptr对象,都需要加锁。
为什么尽量使用make_share()
申请被管理对象以及引用计数的内存;调用适当的构造函数初始化对象;返回一个share_ptr。make_share()节省一次内存分配,原来需要给对象和引用计数各分配一个内存,现在用make_share()的话,一次分配足够大的内存,供对象和引用计数容身。
weak_ptr
weak_ptr更像是share_ptr的一个助手,它指向一个由share_ptr管理的对象但不影响share_ptr的引用计数,无论是否由weak_ptr指向,只要最后一个指向对象的share_ptr被销毁,对象就会被释放。
- 如何创建weak_ptr
创建一个weak_ptr需要一个share_ptr来初始化,由于是弱共享,不会影响share_ptr的引用计数。
int main()
{
share_ptr<int> sp(new int(5));
weak_ptr<int> wp(sp);
return 0;
}
- 判断weak_ptr指向对象是否存在
如果对象存在,lock()函数返回一个指向共享对象的share_ptr,否则返回一个空的share_ptr,还提供了expired()函数来判断对象是否被销毁。 - 如何使用weak_ptr
weak_ptr没有重载‘->’和‘*’操作符,因此不可以直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得share_ptr示例,进而访问原始对象。
scoped_ptr
头文件:“boost/scoped_ptr.hpp”
scoped_ptr是一个类似于auto_ptr的智能指针,包装了new操作符在堆上分配动态对象,能够在保证在任何时候都能将动态分配的对象正确的删除。scoped_ptr的所有权不能移交给别的对象,并且不允许复制和赋值操作。
- 不需要进行delete操作,因为在scoped_ptr对象超出作用域的时候,编译器会调用scoped_ptr对象的析构函数将动态分配的对象删除,将资源返回给系统。
- scoped_ptr不允许复制(拷贝)、赋值。
- scoped_ptr类重载了*和->操作符,没有重载++ 或 – 等操作符。
与auto_ptr区别
- 根本区别在于指针的所有权。auto_ptr被设计成指针的所有权可以转移给其他对象(通过复制或赋值);但是scoped_ptr对象不能转移指针的所有权,一直独占指针的所有权(除了使用reset成员函数重置内部指针所指的对象,又或者使用swap成员函数交换指针的所指的对象);
- auto_ptr的对象可以被作为副本而构造新对象,也可以赋值。但是scoped_ptr禁止复制或赋值(因为复制构造函数和赋值操作符被声明为私有的,但是没有被定义);