智能指针 in c++
智能指针会自动的释放内存,实现自动化
用智能指针,当调用new
时,而不需要调用delete
,而甚至在很多情况下使用智能指针,我们甚至不需要调用new
而智能指针本质上是一个原始指针的包装,当创建一个智能指针,它会调用new
并为你分配内存,然后这些内存会在某一时刻自动释放
🍅最简单的智能指针:unique_ptr
unique_ptr
是作用域指针,超出作用域时,它就会被销毁,然后调用delete
为什么叫unique_ptr
呢
- 因为它必须是
unique
,它必须是唯一的。你不能复制一个unique_ptr
,因为如果你复制一个unique_ptr
,那么就会有两个unique_ptr
指向同一个内存块,而其中一个指针无了,则它会释放那段内存,那么第二个unique_ptr
就会指向了已经被释放了的内存。故不能复制unique_ptr
unique_ptr
是你想要有一个作用域指针的时候,它是你唯一的参考
用法:std::unique_ptr<模板参数> 名字
-
包括
memory
头文件,#include <memory>
-
//一般智能指针 std::unique_ptr<int> temp1 = new int; //声明类的智能指针 std::unique_ptr<Entity> temp2(new Entity()); //这里小懵 //初始化的方式可以有:1.用=赋值运算符初始化 2.用()小括号初始化 //3.用{}大括号初始化 //在一些不可以复制的对象的初始化上,只能用小括号来初始化(比如智能指针)
但是在
unique_ptr
中调用new
可能会有异常安全(很迷,总之就是不推荐调用new
)💡然而,另外一个更好的办法是std大法好,一个函数叫
make_unique<>()
和make_pair<>()
有异曲同工之妙,好处是不用再起变量名,直接造一个对象出来#include <memory> class Entity {}; int main() { std::unique_ptr<Entity> entity = std::make_unique<Entity>();//直接造一个指向Entity内存空间的指针 return 0; }
这个函数对于
unique_ptr<>()
很重要,主要原因是异常安全,故最好的方式就是调用make_unique
,因为如果构造函数恰好抛出异常,它会稍微安全一些,写程序的人最终不会得到一个没用引用的悬空指针,从而造成内存泄漏(注:make_unique<>()
是在C++14引入的,C++11并不支持)效果:这样用智能指针会创造一个
Entity
类的内存空间然后让指针指向它。如果离开了智能指针的作用域,智能指针就会把自己创建的这个类的内存空间删除(原理是智能指针是一个类,在离开作用域时,智能指针会调用自己的析构函数删除自己,而析构函数里的内容还有一个就是delete
新创建的内存空间,这样子就完成了自动化)
unique_ptr
很简单,用处很大,开销也很小(几乎没有),但是有个致命的缺点是无法分享,无法复制,那如果我想完成这两个操作又想有智能指针的自动化流程呢?
🍅共享指针:shared_ptr
shared_ptr
的工作方式是通过引用计数,引用计数基本上是一种方法,可以跟踪你的指针有多少个引用,一旦引用计数到达零,它就会被删除(两个共享指针,则引用计数为2,一个无了,则引用计数为1,而再无了最后一个,引用计数归零,则会释放掉内存)
用法:std::shared_ptr<模板参数> 名字
-
首先也要包含头文件
#include <memory>
-
💡相仿上面,共享指针也有一个std的函数:
std::make_shared<>()
#include <memory> class Entity {}; int main() { int temp = 1; std::shared_ptr<int> a = std::make_shared<int>(temp);//注意这里不是&temp,好像只有在赋值运算符用的时候&才会被识别成取地址运算符 //比如 a = &b;这个才是取地址 //而在这里&temp会被识别成引用,所以要注意啊 //上面也可以写成 //std::shared_ptr<int> a = std::make_shared<int>(); //a = &temp; //只不过在括号内可以直接初始化了 std::shared_ptr<Entity> b = std::make_shared<Entity>(); return 0; }
-
在
unique_ptr
中不建议用new
是因为异常安全,而在共享指针里同样不建议用new
,但在二者原因有所不同,因为共享指针需要分配另一块内存,叫做控制块,用来储存引用计数。如果用new
,那他必须要做两次内存分配:先做一次new Entity
的分配,然后是shared_ptr
的控制内存块的分配。而如果用make_shared<>()
,就能把这两步操作组合起来,这样更有效率 -
{ std::shared_ptr<Entity> temp1; { std::shared_ptr<Entity> temp2 = std::make_shared<Entity>(); temp1 = temp2;//此时temp1和temp2指向同一块内存!引用计数 }//当这个作用域结束,temp2无了,但是temp1还在,所以那块内存还是会保留 }//当这个作用域结束了,temp1无了,则这块内存就会被释放! //当所有的引用都消失了,则底层的Entity就会被删除
共享指针是有一点开销的,因为它的引用计数系统
🍅弱指针:weak_ptr
(搭配共享指针)
用法:std::weak_ptr<模板参数> 名字
💡💡💡当用一个共享指针赋值给另一个共享指针时,引用计数会增加,但是共享指针赋值给弱指针时,引用计数并不会增加
std::shared_ptr<Entity> temp1 = std::make_shared<Entity>();
std::weak_ptr<Entity> temp2;
temp2 = temp1;//把temp1的值赋给弱指针,引用计数不会增加!
使用目的:
std::weak_ptr<Entity> temp2;
{
std::shared_ptr<Entity> temp1 = std::make_shared<Entity>();
temp2 = temp1;//把temp1的值赋给弱指针,引用计数不会增加!
}//作用域结束,因为引用计数只有一个,所以释放Entity内存空间
//在这个时候,弱指针会指向一个无效的Entity内存空间
弱指针的思想就是:如果底层对象还活着,那就可以拿弱指针干想做的事,但是弱指针不会让底层对象保持存活!因为弱指针不会增加引用计数(并不关心Entity是否存活,我只是需要储存它的一个引用罢了)
使用总结
当想声明一个堆分配的对象,但是我不希望我自己亲自来清理它(或者显式的用delete
来清理),则去用智能指针。如果用,就尽量使用unique_ptr
,因为它有一个较低的开销。但如果需要在对象之间共享,就用shared_ptr