C++11中的智能指针和垃圾回收使用

在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函数显式的告诉垃圾回收器,你不要取释放这块空间

作者:Shawn-Summer原文地址:https://blog.csdn.net/m0_71009069/article/details/128806869

%s 个评论

要回复文章请先登录注册