智能指针
1.什么是智能指针?
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露(利用自动调用类的析构函数来释放内存)。
实现技术是使用引用计数(shared_ptr)、资源独占(unique_ptr和auto_ptr)、只引用,不计数(weak_ptr)。
指针和智能指针:
智能指针是对象,对象在过期时,会调用其析构函数析构掉,而常规指针,当其指向堆内存,最终需要人为的delete
2.为什么需要智能指针?
(1)new和delete管理动态内存存在的三个常见问题:
1)忘记delete内存
2)使用已经释放掉的对象
3)同一块内存释放两次
(2)中途抛出异常,无法释放资源。
3.智能指针的原理
创建一份资源出来的时候,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。除此之外,通过运算符重载(重载*和重在-> 等),可以像指针一样使用。
4.3种智能指针的实现策略/实现技术
(1)auto_ptr和unique_ptr:
采用的是单独所有权的概念,对于特定对象,只能被一个智能指针所拥有,这样,只有拥有该对象的智能指针的析构函数才会删除该对象,但要注意的是,赋值操作会转让操作权。
(2)shared_ptr
shared_ptr则采用引用计数的策略,共享所有权,即可以让多个指针指向同一个对象,例如,每次创建类的新对象时,初始化指针并将引用计数置为1;赋值时,计数+1,指针过期时,计数-1.只有当计数为0时,即当最后一个shared_ptr离开作用域时,才会释放这块内存。
引用计数增减的情况:
注意:shared_ptr本身不是线程安全的,shared_ptr的计数功能是原子的,但对象的读写不是原子的。C++
标准也只是保证的是shared_ptr的lock()指针提升是线程安全的。所以,要实现线程安全,可能需要weak_ptr
与shared_ptr
配合使用,
5.shared 和 unique区别
(1)unique_ptr具有唯一性,对指向的对象值存在唯一的unique_ptr。而可以有多个shared_ptr指向同一对象
(2)unique_ptr不可复制,赋值(即将要销毁的对象除外,如函数的返回值,或局部对象的返回,C++14后可以使用std::move()转换对象的所有权),shared_ptr可以
(3)与shared_ptr相比,若自定义删除器,需要在声明处指定删除器类型,而shared不需要,shared自定义删除器只需要指定删除器对象即可,在赋值时,可以随意赋值,删除器对象也会被赋值给新的对象。
原因:
unique的实现中,删除器对象是作为unique_ptr的一部分,而shared_ptr,删除器对象保存在control_block中。
其他细节参考我的ProcessOn的思维导图总结:https://www.processon.com/view/link/5c87c263e4b0c996d3605c3a
拓展:
shared_ptr除了用于管理纯粹的内存之外还可以用于其他的目的,比如管理FILE、SOCKET等,极大的增加了编程的方便性。而这里就是利用了向其中传入删除器,如:
void auto_run_fun(FILE* f){
printf("auto running.\n");
fclose(f);
}
void my_shared_ptr2(){
FILE* f = fopen("data.txt","w");
std::shared_ptr<FILE> file_ptr(f, auto_run_fun);
}
6.unique_ptr相比auto_ptr的优势
(1)unique_ptr相比auto_ptr更安全
当两个指针指向同一个对象时,程序出现删除该对象两次的情况时,程序会编译出错,而auto_ptr则会在运行阶段才出现“Segmentation fault”(段错误)
示例程序:
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
auto_ptr<int> px(new int(8));
//unique_ptr<int> px(new int(8));
auto_ptr<int> py;
//unique_ptr<int> py;
cout << "(1)px= " << *px << endl;
py = px; //将px控制权交给py,px变为空指针
cout << "(2)px= " << *px << endl;
}
使用auto_ptr的运行结果:运行时报错
将auto_ptr替换为unique_ptr后:编译时报错
要安全的重用unique_ptr指针,可给它赋新值。C++为其提供了std::move()方法(C++14新增)
由于unique_ptr使用了C++11新增的移动构造函数和右值引用,所以可以区分安全和不安全的用法。
(2)unique_ptr相较auto_ptr和unique_ptr,提供了可用于数组的变体
auto_ptr和shared_ptr可以和new一起使用,但不可以和new[]一起使用,但是unique_ptr可以和new[]一起使用
std::unique_ptr<int[]> uptr(new int[10]);
//std::unique_ptr<int[]> uptr = new int[10];//错误
7.weak_ptr的引入为了解决shared_ptr的循环引用问题
循环引用问题:
对weak_ptr的解释:
(1)C++11的weak_ptr,弱引用。 引用计数有一个问题就是互相引用形成环,这样两个指针指向的内存都无法释放。需要手动打破循环引用或使用weak_ptr。
(2)weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。
(3)如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。
(4)所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。判断weak_ptr的对象是否失效的方法如下:
1、expired():检查被引用的对象是否已删除。
2、lock()会返回shared指针,判断该指针是否为空。
3、use_count()也可以得到shared引用的个数,但速度较慢。
(5)weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
8.智能指针的选择
(1)如果程序中要使用多个指向同一个对象的指针,那么应该使用shared_ptr;比如说现在有一个包含指针的STL容器,现在用某个支持复制和赋值操作STL算法去操作该容器的指针元素,那么就应该用shared_ptr。不能用unique_ptr(编译器报错)和auto_ptr(行为不确定)。
(2)如果程序中不需要使用多个指向同一个对象的指针,则可使用unique_ptr;如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。
(3)虽然auto_ptr仍是标准库的一部分,但编写程序时应该使用unique_ptr
(4)在unique_ptr为右值时,可将其赋给shared_ptr,举例如下:
//注:make_int()的返回类型为unique_ptr
unique_ptr<int> pup(make_int(rand() % 1000));
shared_ptr<int> spp(pup); //错误,pup是一个左值unique_ptr,不能对它进行拷贝
shared_ptr<int> spr(make_int(rand() % 1000)); //正确,调用make_int()为一个右值unique_ptr
//模板shared_ptr中有包含显式构造函数,可用于将右值unique_ptr转换为shared_ptr
(5)在编译器中没有提供unique_ptr时,可以考虑使用BOOST库提供的scoped_ptr,它与unique_ptr类似
9.unique_ptr和shared_ptr的实现
unique_ptr实现:
//unique_ptr的实现
shared_ptr实现:
//shared_ptr的实现
智能指针有参考的博文:https://blog.youkuaiyun.com/derkampf/article/details/72654883
https://zhuanlan.zhihu.com/p/35520779
https://blog.youkuaiyun.com/weizhengbo/article/details/68957993