1.shared_ptr
shared_ptr类似于vector,也是一个模板类,包含于头文件memory中,基本的定义方式如下
#include <memory>
shared_ptr<int> ptr = make_shared<int>();//ptr指向值为0的int
shared_ptr<int> ptr = make_shared<int>(1);//ptr指向值为1的int
shared_ptr<vector<int>> ptr = make_shared<vector<int>>(2,1);//ptr指向1个有2个值为1的vector<int>
make_shared是一个函数,通常可以用auto来定义一个变量保存make_shared的结果,这样写起来比较方便
auto ptr = make_shared<vector<int>>();
每个shared_ptr都有一个关联的计数器,通常称为引用计数
以下情况计数器会增加
- shared_ptr类型数据初始化另一个shared_ptr类型数据
- shared_ptr类型数据作为参数传递给函数
- shared_ptr类型数据作为函数的返回值
以下情况计数器会减少
- 智能指针被赋予新值
- 智能指针被销毁时,例如局部的shared_ptr数据离开作用域
举例说明下上述两种情况
shared_ptr<int> ptr = make_shared<int>(1);
shared_ptr<int> p = make_shared<int>(2);
shared_ptr<int> p(ptr);
// 这时p被赋予新值,指向p的引用计数会减少,而指向ptr的引用计数会增加(相当于p被同化了,变成自己人了,所以对方减少,我方增加),如果p指向的引用计数为零时,p指向的内存将会被释放
shared_ptr<int> fun(T arg)
{
...... //对arg的处理
return make_shared<int>(arg);
}
void use_fun(T arg)
{
shared_ptr<int> p = fun(arg);//当p离开局部作用域时,它指向的内存将会自动释放掉
}
//由于p是唯一引用这块内存的fun函数返回内存的对象。由于p要销毁,所以他指向的对象也会被销毁shared_ptr会通过析构函数来释放指向的内存
void use_fun(T arg)
{
shared_ptr<int> p = fun(arg);
return p;
}
//由于return时返回一个p的拷贝,故当p离开作用域时,它所指向的内存还有其他的使用者,计数器也就不会为零
当计数器为零时,shared_ptr会通过析构函数来释放指向的内存,有效的减少了因为忘记delete而造成的内存泄漏。
shared_ptr还可以和new结合使用
shared_ptr<int> p1 = new int();//错误
shared_ptr<int> p1(new int())//正确,使用直接初始化形式
上述错误是因为shared_ptr构造函数时explicit的,不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化,下述也是同样的原因
shared_ptr<int> clone(int p)
{
return new int(p)//错误
}
shared_ptr<int> clone(int p)
{
return shared_ptr<int>(new int(p));//显式强制转换成shared_ptr<int>类,正确
}
智能指针和异常
为使发生异常后资源能被正常释放,智能指针是不二选择
void f1()
{
shared_ptr<int> p(new int(1));
....//然后抛出异常
}//函数结束时会正常释放资源
void f2()
{
int *ip = new(int(1));
...//此处发生异常
delete ip;
}//在delete之前出现异常,无法执行delete语句,ip所指向的内存不会被释放
常用接口
name | function |
---|---|
ptr.use_count() | 返回与ptr共享对象的智能指针数量,可能较慢,主要用于调试 |
注意事项:
- 但是还是推荐大家使用make_shared来初始化shared_ptr类对象,混用内置指针和智能指针容易出现问题,尤其是使内置指针指向智能指针所负责的对象,因为智能指针会自动释放内存,当智能指针释放这块内存后,再使用内置指针来进行操作是一种未定义的行为。
- 不要使用get()函数初始化另一个智能指针或为另一个智能指针赋值,两个智能指针指向同一块内存,当一个指针被销毁时,内存释放,另一个指针的操作也是一种未定义行为
2.unique_ptr
unique_ptr与shared_ptr不同,unique_ptr在某一个时刻只能有一个unique_ptr指向给定对象。当unique_ptr被销毁时,它所指向的对象也将被销毁
其基本定义方式如下
unique_ptr<string> p1(new string("hello world"));//p1指向值为hello world的string
因为unique_ptr不支持拷贝和赋值,所以以下操作错误
unique_ptr<string> p2(p1);//错误
unique_ptr<string> p3 = p1;//错误
那该怎么进行unique_ptr类变量的相互传值呢
unique_ptr<string> p2(p1.release());//将p1的所有权转移给p2,p1置空
unique_ptr<string> p3(new string("linux"));
p2.reset(p3.release());//reset释放了p2所指向的内存,把p3的值给p2,使得p2指向p3原本指向的内存,p3因为release置空
虽说unique_ptr不能拷贝和赋值,但是将要销毁的是例外,其实这算一种特殊的"拷贝",属于移动操作
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p))
}
name | function |
---|---|
u.release() | u放弃对指针的控制权,返回指针,并将u置空 |
u.reset() | 释放u所指向的对象 |
u.reset(q) | 如果提过了内置指针q,另u指向这个对象;否则将u置空 |
unique_ptr与动态数组
定义方式如下
unique_ptr<int[]> ptr1(new int[3]);
unique_ptr<int[]> ptr2(new int[3]{1,2,3});
unique_ptr指向一个数组时,不能使用点和箭头运算符,因为指针指向的是一个数组而不是单个对象,当我们想访问数组时,可以通过下标来访问
for(auto i = 0; i < n; i++)
{
cout << ptr2[i] << endl;
}
补充下早期的auto_ptr类,它具有unique_ptr类功能的一部分,虽然auto_ptr扔是标准库的一部分,在编写程序时应该用unique_ptr代替
网友评论