C++11中的智能指针和垃圾回收使用
作者:Shawn-Summer
在C/C++中,我们需要自己管理动态内存区,我们在写代码中可能会出现如下3中内存管理的缺陷
- 野指针:内存单元已经释放,但是指向它的指针还在使用
- 重复释放:试图是释放已经释放过的内存单元
- 内存泄漏:不再使用的内存单元没有进行释放
C++恶心的地方就在于它存在指针,需要编写者自己管理内存,所以内存上面的问题就会有很多,但是在其他语言,例如python,java,C#,他都不存在指针概念,也就意味著你不需要开辟释放内存这些操作。而正因为C++将指针暴露出来,甚至将右值引用暴露出来,才使得C++的运行效率非常快。
为了减少C++中的内存问题,就出现了智能指针,它是一种对C风格指针的优化,它把内存的释放放在了智能指针的析构函数中,这样子就能减少一部分自己手动释放内存的代码。
1.C++11中的unique_ptr
#include<memory> #include<iostream> using namespace std; int main() { unique_ptr<int> up1(new int(11)); unique_ptr<int> up2=up1;//无法通过编译 cout<<*up1<<endl;//11 unique_ptr<int> up3=move(up1);//现在up3是数据的唯一指针 cout<<*up3<<endl;//11 cout<<*up1<<endl;//运行错误 up3.reset();//释放内存 up1.reset();//不会重复释放内存 cout<<*up3<<endl;//运行错误 }
我们知道unique_ptr正如它的名字一样,它表示一个对象只能由一个指针绑定,不允许一个对象同时又多个unique_ptr绑定。
而且 unique_ptr只存在移动语义,而不存在拷贝语义 ,我们看上面代码中unique_ptr<int> up3=move(up1);,在unique_ptr中只存在移动构造函数和移动赋值函数,不存在拷贝构造函数和拷贝赋值函数。所以说我们只能用右值来构造或赋值unique_ptr。
还有一种初始化unique_ptr的方法就是:make_unique<>(),相较于使用new初始化,前者内存碎片化更少,在现代C++种主要使用,make_unique。
实际上,C++98中的auto_ptr和C++11中的unique_ptr实现的是同一个东西,但是在C++98中我们不存在移动语义,所以auto_ptr它是存在拷贝构造函数和拷贝赋值函数的,所以诸如:
auto_ptr<int> up2=up1;是可以通过编译的,在C++11中我们废弃掉了,auto_ptr也是这个原因。
2.C++11中的shared_ptr和weak_ptr
shared_ptr是一种共享式的指针,它采用引用计数的方式,来决定何时释放内存,引用计数就是说,它统计每个对象有几个指针指向它。一旦一个对象的引用计数为0,即不存在指向它的指针,那么就释放它。
weak_ptr是用来验证shared_ptr指向的内存单元的有效性的,被它指向的对象的引用计数不会增加。
#include<memory> #include<iostream> using namespace std; void Check(weak_ptr<int>& wp) { shared_ptr<int> sp=wp.lock(); if(sp!=nullptr) cout<<"still "<<*sp<<endl; else cout<<"pointer is invalid."<<endl; } int main() { shared_ptr<int> sp1=make_shared<int>(22); shared_ptr<int> sp2=sp1; weak_ptr<int> wp=sp1; cout<<*sp1<<endl; cout<<*sp2<<endl; Check(wp); sp1.reset(); cout<<*sp2<<endl; Check(wp); sp2.reset(); Check(wp); }
22
22
still 22
22
still 22
pointer is invalid.
3.垃圾回收
虽然智能指针能够帮助用户有效管理堆内存,但是它还是需要显式声明智能指针,而完全不需要指针的内存管理方法也会更讨人喜欢。这种方法就是垃圾回收机制,写代码的时候不需要开辟释放内存操作,这些操作都由编译器自动实现,这种智能化的方案就是垃圾回收机制。
遗憾的是,C++不支持垃圾回收机制。
垃圾回收的方式有4种
基于引用计数的方法
其实就是和shared_ptr一样的方式,就是一旦对象的引用次数为0就释放它,python就是使用的这种方案,不过这种方案不好,它效率比较低,一旦对象创建,或者有指针指向它,都要计算引用此时,而且它不能解决"环形引用"问题
标记-清除
这种方法就是存在一个根对象,它管理所有对象,依次遍历每个对象,给它们引用的区域打上标记,然后遍历完成后,把所有没有标记的区域释放掉,这种方案的缺陷在于会存在大量的内存碎片
标记-整理
它是在标记-清除方案的基础上,标记完后不再遍历释放垃圾了,而是所有被标记的区域,向左靠齐,这样就减少了内存碎片
标记-拷贝
它是将内存空间分为两块:From和to,刚开始就从From空间种分配内存,一旦From内存满了,就把From空间中所有活对象,拷贝到to空间中,而且都是向左靠齐的,然后再将From和to的角色互换。
很遗憾C++11目前还没有公开支持过垃圾回收,不过有些库和有限编译器支持了部分垃圾回收的功能
int main() { int *p=new int; p+=10; p-=10; *p=10; }
上面代码中,一旦p指向了其他区域,如果你的编译器支持垃圾回收,例如采用的引用计时方式,那么一旦p移到了其他地区,这个开辟的new int空间,就会被释放,更危险的是,这块空间会被其他线程使用,这时候,p如果又指回了原来的地方,那么p就是一个野指针。
为了防止,new int这块空间被垃圾回收器回收掉,我们的一种方案是:
int main() { int *p=new int; declare_reachable(p); p+=10; p-=10; *p=10; }
这里的declare_reachable函数显式的告诉垃圾回收器,你不要取释放这块空间
到此这篇关于C++11中的智能指针和垃圾回收使用的文章就介绍到这了,更多相关C++11 智能指针和垃圾回收内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!