1、介绍
C++11新标准有三个智能指针类:shared_ptr、unique_ptr和weak_ptr。这三个类都在头文件memory中。
智能指针的作用主要是防止内存泄露,使用智能指针可以防止用new申请内存但是忘记delete从而造成内存泄露。使用智能指针,内存在不使用的时候,会自动释放。
2、shared_ptr
shared_ptr允许有多个shared_ptr指向同一块内存,有n个shared_ptr指向同一块内存,引用计数就为n,当n = 0的时候,shared_ptr就会释放所使用的内存。
声明一个shared_ptr:
shared_ptr<string> sp1;//sp1可以指向一个string
shared_ptr<vector<int>> sp2;//sp2可以指向一个vector<int>
智能指针的使用方式和普通指针一样,解指针引用返回它指向的对象,在条件判断语句中使用它,就是判断其是否为空。
if(sp2 && sp2->empty())
{
(*sp2).push_back("123");//向sp2指向的vector中加入string"123"
}
shared_ptr的常用函数:
sp.get() /*返回sp中保存的指针,可以是内置指针类型*/
swap(p, q) /*交换p和q中指向的指针,非成员函数*/
p.swap(q) /*交换p和q中指向的指针,是智能指针类的成员函数*/
shared_ptr<T> p(q) /*p是q的拷贝,这会增加q的引用计数*/
p = q /*p和q都是shared_ptr,或者能够相互转换,此操作会递减p的引用计数,增加q的引用计数*/
p.unique() /*若p的共享对象数量为1,则返回true,否则为false*/
p.use_count() /*返回与p共享对象的智能指针数量*/
make_shared函数:
shared_ptr最安全分配和使用动态内存的方法是使用库函数make_shared,这个函数是在动态内存中分配一块内存并初始化它,返回这个对象的shared_ptr。
shared_ptr<int> p1 = make_shared<int>();
shared_ptr<int> p2 = make_shared<int>(222);
shared_ptr<string> p3 = make_shared<string>(10, '3');
auto p4 = make_shared<string>(10, '4');
shared_ptr的引用计数:
引用计数递增的情况:
(1)用一个shared_ptr初始化另一个shared_ptr
(2)赋值
(3)将一个shared_ptr作为一个参数传递给一个函数
(4)作为一个函数的返回值
引用计数递减的情况:
(1)给一个shared_ptr赋新值
(2)shared_ptr被销毁(如离开函数作用域)
当一个shared_ptr的引用计数为0的时候,就会调用析构函数销毁对象,并释放其占用的内存。
shared_ptr的通过引用计数这样的机制来实现内存的释放,可以用来实现类对象成员的数据共享。若我们一个类对象需要共享同一份数据,我们可以声明成static,而用shared_ptr也可以实现。
shared_ptr和new结合使用:
new运算符返回的指针不能转化成一个智能指针,因此要使用直接初始化。
shared_ptr<int> p = new int(1024);//错误
shared_ptr<int> p(new int(1024));//正确
智能指针默认使用delete来释放它所关联的对象,因此如果new出来的对象的释放操作不是delete,则需要定义自己的释放操作。
shared_ptr<T> p(q, d) //p接管了内置指针q指向的对象的所有权,p将会使用可调用对象d释放指向的对象
shared_ptr<T> p(p2, d) //p是p2的拷贝,唯一的区别是p会使用d来释放对象
p.reset() //如果p的引用计数是1,则释放对象;否则p就只是置为空
p.reset(q) //如果p的引用计数是1,则释放对象并指向q;否则就只是指向q
p.reset(q, d) //和上的操作的唯一区别是会调用d来释放q
3、unique_ptr
unique_ptr“拥有”它所指向的对象,某个时刻只有一个unique_ptr指向一个给定的对象,因此一个unique_ptr不支持普通的拷贝或者赋值操作。
声明一个unique_ptr:
unique_ptr<int> p1;
unique_ptr<int> p2(new int(1024));
unique_ptr<string> p3(new string("1024"));
unique_ptr<int> p4(p2); //错误,unique_ptr不支持拷贝
p1 = p2; //错误,unique_ptr不支持赋值
unique_ptr的常用函数:
unique_ptr<T, D> u; //unique_ptr指向的对象的类型为T,将会调用类型为D的可调用对象来释放对象
unique_ptr<T, D> u(d); //unique_ptr指向的对象的类型为T,将会调用类型为D的可调用对象d代替delete来释放对象
u.release(); //u放弃对指针的控制权,返回指针,并将u置空
u.reset(); //释放u指向的对象
u.reset(q); //释放u指向的对象,并指向这个对象(q指向的)
u.reset(nullptr); //u释放指针的所有权,指向nullptr
使用release函数和reset函数可以转移指针的所有权。
unique_ptr<string> p1(new string("p1"));
unique_ptr<string> p2(new string("p2"));
p2.reset(p1.release()); //p1释放对指针的所有权,并置为空;p2释放掉所指向的对象,指向p1指向对象
函数返回unique_ptr:
我们可以拷贝或者赋值一个将要被销毁的unique_ptr
//从int*创建一个unique_ptr返回
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p));
}
//返回局部对象的拷贝
unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int(p));
return ret;
}
向unique_ptr传递删除器:
unique_ptr中也是默认使用delete进行释放对象,我们可以向unique_ptr指定删除器。
调用格式:
connection c = connect(&d);
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
尖括号中的两个参数是类型,其中decltype(end_connection)返回一个函数类型,所以必须添加一个*号指出使用的类型是函数指针。
3、weak_ptr
weak_ptr不控制所指向对象的生存周期,它指向一个由shared_ptr管理的对象,并且不会改变其引用计数,因此即使有weak_ptr指向对象,shared_ptr的引用计数为0的时候还是会释放对象。
声明一个weak_ptr
当我们要创建一个weak_ptr的时候,需要用一个shared_ptr去初始化它。
auto sp = make_shared<int>(100);
weak_ptr<int> wp(sp);
weak_ptr常用函数
wp = sp; //sp可以是shared也可以是weak
w.reset(); //将w置空
w.use_count(); //返回与w共享对象的shared_ptr的数量
w.expired(); //如果w.use_count()为0,返回true,否则返回false
w.lock(); //如果w.expired()为true,返回一个空shared_ptr,否则返回一个指向w对象的shared_ptr
由于weak_ptr指向的对象可能不存在,所以不能直接访问,需要通过lock函数检查对象是否仍然存在。
if(shared_ptr<int> sp = wp.lock())
{
//访问对象
}
weak_ptr常用于作为一个核查指针。
4、智能指针和动态数组
内存泄露常见的情况是使用new申请了一个比较大的动态数组,但是忘记了delete,造成了比较大的内存泄露问题,使用智能指针指向动态数组,可以防止这个问题。
标准库提供了管理new分配的数组的unique_ptr版本,使用方式如下:
unique_ptr<int[]> up(new int[1024]);
for(int i = 0; i<1024; ++i)
{
up[i] = i;//此时箭头和.号没有作用,因为up指向的是一个数组
}
up.release();//up会自动调用delete[]释放数组
使用shared_ptr也可以管理动态数组,但是标准库没有提供支持,所以需要使用shared_ptr管理动态数组的时候需要提供自定义的删除器。
shared_ptr<int[]> sp(new int[1024], [](int* p) {delete[] p;});
for(int i = 0; i<1024; ++i)
{
*(sp.get() + i) = i; //因为sp内置不支持管理动态数组,因此这里的使用需要先解引用
}
sp.reset();//调用reset的时候就使用我们定义的lambda表达式释放动态数组
智能指针和异常:
使用智能指针的时候,如果程序块过早结束,智能指针也能在内存不需要的时候释放
使用内置指针的时候:
void f()
{
int *p = new int[100];
//代码抛出异常,并且没有被捕获
delete[] p;
}
//这样指针p指向的内存会得不到释放
使用智能指针可以确保上述情况内存得到释放
void f()
{
shared_ptr<int[]> sp(new int[100], [](int* p) {delete[] p;});
//代码抛出异常,并且没有被捕获,内存会自动释放
}
不要混用智能指针和普通指针:
混用智能指针和内置指针,容易造成程序错误。
void process(shared_ptr<int> sp)
{
cout << *sp << endl;
}//sp离开作用域,被销毁
int main()
{
int* x(new int(1024));
process(shared_ptr<int>(x)); //传入函数的时候,sp的引用计数是1,离开作用域之后,变为0,会释放内存,此时x指向的内存已经被释放
int j = *x; //错误,x是野指针
}
总结
尽量使用智能指针代替内置指针,千万不要智能指针和内置指针混用。