C++智能指针

在C/C++中,经常需要用到指针,这也就涉及到相应的内存操作。而操作不当又会出现内存泄漏、访问野指针、多次释放等问题。故而C++中引入了智能指针的概念,方便其对堆内存进行管理。简单地说,智能指针的本质就是一个类模板,当一个类超出自己的生命周期之后,就会调用自己的析构函数,因此智能指针在析构函数里面去释放所指向的内存,这样也就可以起到智能指针自动回收内存的效果。其头文件为#include<memory>

C++98中,有auto_ptr的智能指针。

 

其部分源代码如下:

从中我们也可以看出auto_ptr的本质也就是一个类模板。

 

这里假如我们在一个函数中有:

double *p=new double;

如果最后我们没有delete p(释放p所指向的内存);那么每调用一次这个函数就会新建一个double大小的空间。(还有其他各种情况导致没有执行到delete语句就完结或者跳转,同样会造成内存泄漏)

而auto_ptr就是为了解决这种问题出现的:

std::auto_ptr<double> pd(new double);

最后其生命周期结束之后会调用析构函数自动回收所指向的内存。

 

相关函数

get()         返回所存储的指针的值(由于pd并非指针而是类,所以要得到指针的值需要get函数,但是由于操作符重载了*pd即为指向空间中存放的数据)

reset()        重置指针,此时pd内部指针所指向的内存被销毁,同时使其值为nullptr。

release()      释放所有权,此时pd内部指针指向nullptr,但之前所指向的内存不会被销毁。

*但auto_ptr存在着问题,即auto_ptr允许一个auto_ptr指针指向另一个auto_ptr指针所拥有的对象,语法上不会报错。但是,当一个指针删除该对象后,而其他指针仍然指向一个已被删除的对象,此时对其进行读写操作就会出错(这个问题就如同普通指针一样)。正因为存在这个问题,所以也就有了后来的unique_ptr。

 

 

在C++11中加入了shared_ptr, weak_ptr, unique_ptr三种智能指针。(自然同样也是类模板)

首先说unique_ptr

unique_ptr 独占所指向的对象,同一时刻只能有一个 unique_ptr 指向给定对象。并且禁止拷贝语义,只有移动语义实现改变内部指针指向。(即两个unique_ptr指针指向同一个空间的时候会报错)

中get、reset、release函数仍然同上,但是其不能通过等号赋值给相同类型的对象了。如果想要将一个unique_ptr的值给另一个unique_ptr的话,可以使用move函数pd2=move(pd),这时pd对指向空间的控制权全部交给pd2了,自己指向nullptr。(move函数就是将一个左值作为参数,返回其右值引用。)

 

然后说shared_ptr

shared_ptr 是为了解决unique_ptr 在对象所有权上的局限性(unique_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针。shared_ptr对象除了包括一个所拥有对象的指针外, 还必须包括一个引用计数代理对象的指针。

中get函数和release函数仍然同上,reset函数稍有不同(数个shared_ptr对象的内部指针指向一个内存,若一个对象调用reset会将其引用计数减一,然后该对象指向空,此时那个内存并不会被销毁。只有当引用计数减到0的时候才会删除所指向的内存。通俗地说,当还有额外shared_ptr类的指针指向一个内存的时候,reset并不会销毁该内存,但如果只有这一个shared_ptr类的指针指向这里,那么reset之后该内存也会被销毁)。

 

 

最后说weak_ptr

weak_ptr的出现完全是为了弥补shared_ptr的缺陷,其实相比于上一代的智能指针auto_ptr来说, shared_ptr可以说近乎完美,但是通过引用计数实现的它,虽然解决了指针独占的问题,但也引来了引用成环的问题。

对于引用成环的问题我们看下面代码:

#include<iostream>
#include<memory>
using namespace std;

class B;

class A
{
public:
  shared_ptr<B> m_b;
};

class B
{
public:
  shared_ptr<A> m_a;
};
 
int main()
{
  while (true)
  {
    shared_ptr<A> a(new A); //new出来的A的引用计数此时为1
    shared_ptr<B> b(new B); //new出来的B的引用计数此时为1
    a->m_b = b; //B的引用计数增加为2
    b->m_a = a; //A的引用计数增加为2
  }
 
  //b先出作用域,B的引用计数减少为1,不为0,所以堆上的B空间没有被释放,且B持有的A也没有机会被析构,A的引用计数也完全没减少
 
  //a后出作用域,同理A的引用计数减少为1,不为0,所以堆上A的空间也没有被释放
 return 0;
}

weak_ptr本身也是一个类模板,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用,可以将shared_ptr的对象赋值给weak_ptr,并且这样并不会改变引用计数的值。weak_ptr的函数有lock、swap、reset、expired、operator=、use_count等,与shared_ptr相比多了lock、expired函数,但是却少了get、operator*、operator->等函数。

weak_ptr指向shared_ptr指针指向的对象的内存,却并不拥有该内存。但是,使用weak_ptr成员lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回nullptr。由于weak_ptr是指向shared_ptr所指向的内存的,所以,weak_ptr并不能独立存在。

 

 

最后,通过weak_ptr可以解决环形引用的问题,代码如下:

#include <iostream>
#include <memory>
using namespace std;

class father;
class son;

class father {
public:
    father() {
        cout << "father !" << endl;
    }
    ~father() {
        cout << "~~~~~father !" << endl;
    }
    void setSon(shared_ptr<son> s) {
        son = s;
    }
private:
    //shared_ptr<son> son;
    weak_ptr<son> son; // 用weak_ptr来替换
};


class son {
public:
    son() {
        cout << "son !" << endl;
    }
    ~son() {
        cout << "~~~~~~son !" << endl;
    }
    void setFather(shared_ptr<father> f) {
        father = f;
    }
private:
    shared_ptr<father> father;
};

void test() {
    shared_ptr<father> f(new father());
    shared_ptr<son> s(new son());
    f->setSon(s);
    s->setFather(f);
}

int main()
{
    test();
    return 0;
}

转载请标明出处,原文地址:https://blog.csdn.net/come_from_pluto

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值