有了前面的基础,我们来看stl里提供了哪些智能指针以及是如何使用的,从最原始的C++98中添加的auto_ptr
讲起。
C++新特性19_auto_ptr的使用及废除原因
1. class template: std::auto_ptr目前使用情况
template <class X> class auto_ptr;
从官网链接的文档上就可以看出,这个auto_ptr指针不推荐使用(deprecated),原因这里也有说明:
- auto_ptr指针在c++11标准中就被废除了,可以使用unique_ptr来替代,功能上是相同的,unique_ptr相比较auto_ptr而言,提升了安全性(没有浅拷贝),增加了特性(delete析构)和对数组的支持。
- 这个类模板提供了有限度的垃圾回收机制,通过将一个指针保存在auto_ptr对象中,当auto_ptr对象析构时,这个对象所保存的指针也会被析构掉。
- auto_ptr 对象拥有其内部指针的所有权。这意味着auto_ptr对其内部指针的释放负责,即当自身被释放时,会在析构函数中自动的调用delete,从而释放内部指针的内存。
2 auto_ptr两大缺陷
2.1 auto_ptr下的模板类对象的多次析构
不能有两个auto_ptr 对象拥有同一个内部指针的所有权,因为有可能在某个时机,两者均会尝试析构这个内部指针。
2.1.1 定义一个auto_ptr下的模板类对象
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char* argv[])
{
//通过指针进行构造
std::auto_ptr<int> aptr(new int(3));
//一个取地址 一个拿内容 *aptr作为最原始的对象使用
printf("aptr %p : %d\r\n", aptr.get(), *aptr);
return 0;
}
2.1.2 同时对一个指针定义两个auto_ptr模板对象,两个对象都指向一个地址
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char* argv[])
{
int*p = new int(3);
{
//通过指针进行构造
std::auto_ptr<int> aptr1(p);
std::auto_ptr<int> aptr2(p);
}
return 0;
}
下图中可以看到两个对象都指向了一个地址:
可以推测到一旦出了{}的块作用域,就会崩溃,这是因为一旦出作用域,对象就会析构,同一地址的对象析构两次就会造成崩溃。
这样的写法就与我们早期没有引用计数的指针类似。
2.2 两个auto_ptr对象发生赋值操作时,右者对象会丧失该所有权
当两个auto_ptr对象之间发生赋值操作时,内部指针被拥有的所有权会发生转移,这意味着这个赋值的右者对象会丧失该所有权,不在指向这个内部指针(其会被设置成null指针)。
auto_ptr的构造的参数可以是一个指针,或者是另外一个auto_ptr对象。
- auto_ptr获取了内部指针的所有权后,之前的拥有者会释放其所有权。
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char* argv[])
{
{
//通过指针进行构造
std::auto_ptr<int> aptr1(new int(3));
std::auto_ptr<int> aptr2(new int(111));
aptr2 = aptr1;
printf("aptr1 %p : %d\r\n", aptr1.get(), *aptr1);
}
调试之后可以看到两个指针指向不同的地址
运行之后,aptr1的指针指向为empty,相当于aptr2的内容被释放,aptr1的内容被转移,等价于拷贝移动。
3. auto_ptr的接口(成员函数)
下来来查看auto_ptr的接口(成员函数)
3.1 auto_ptr析构及资源的自动释放release()
在这里可以在使用前调用release,从而放弃其内部指针的使用权,但是同样这么做违背了智能指针的初衷。
#include <iostream>
#include <memory>
using namespace std;
void foo_release()
{
//释放
int* pNew = new int(3);
{
std::auto_ptr<int> aptr(pNew);
int* p = aptr.release();
}
}
int main(int argc, char* argv[])
{
foo_release();
{
//通过指针进行构造
std::auto_ptr<int> aptr1(new int(3));
std::auto_ptr<int> aptr2(new int(111));
aptr2 = aptr1;
printf("aptr1 %p : %d\r\n", aptr1.get(), *aptr1);
}
return 0;
}
3.2 分配新的指针所有权reset()
可以调用reset()来重新分配指针的所有权,reset中会先释放
原来的内部指针的内存,然后分配新的内部指针。
3.3 =运算符的使用
void foo_assign()
{
std::auto_ptr<int> p1;
std::auto_ptr<int> p2;
p1 = std::auto_ptr<int>(new int(3));
*p1 = 4;
p2 = p1;
}
4. 为什么11标准会不让使用auto_ptr,原因是其使用有问题。
4.1 作为参数传递会存在问题
因为有拷贝构造和赋值的情况下,会释放原有的对象的内部指针,所以当有函数使用的是auto_ptr时,调用后会导致原来的内部指针释放。
//p1内的值移动拷贝(剪切)给p,p1里的内容释放
void foo_test(std::auto_ptr<int> p)
{
printf("%d\r\n", *p);
}
int _tmain(int argc, _TCHAR* argv[])
{
//拷贝构造
std::auto_ptr<int> p1 = std::auto_ptr<int>(new int(3));
foo_test(p1);
//这里的调用就会出错,因为拷贝构造函数的存在,p1实际上已经释放了其内部指针的所有权了
printf("%d\r\n", *p1);
return 0;
}
4.2 不能使用vector数组
数组传递值,本质也是移动拷贝
以下代码编译就无法通过
void foo_ary()
{
std::vector<std::auto_ptr<int>> Ary;
std::auto_ptr<int> p(new int(3));
//push_back时不允许如此构造对象
Ary.push_back(p);
printf("%d\r\n", *p);
}
5. 学习视频地址:auto_ptr的使用及废除原因
6. 学习笔记:auto_ptr的使用及废除原因学习笔记