理解C++智能指针

C++智能指针是面试中经常会问到的一个经典知识点,本身使用也具有很大的意义。本文从下面三个方面对智能指针的内容进行整理,以期对智能指针能够有一个较为清晰的认识:
1 智能指针的实现原理
2 常用的智能指针
3 智能指针的实现


1、智能指针的实现原理:
智能指针是一个类,且这个类是个模板类,为了适应不同基本类型的需求,它在构造函数中传入一个普通指针,将这个基本类型指针封装为类对象指针,并在析构函数中释放这个指针,删除该指针指向的内存空间。因为一般使用时,智能指针的类都是局部对象,所以当函数(或程序)自动结束时会自动被释放。

2、 常用的智能指针:
STL一共提供了四种智能指针:auto_ptr,unique_ptr,shared_ptr和weak_ptr

对于所有的智能指针需要注意以下几点
1) 所有的智能指针类都有一个explicit构造函数(阻止不应该允许的经过转换构造函数进行的隐式转换的发生,即不能在隐式转换中使用),因此不能直接将指针转换为只能指针对象,必须显式调用,即

int* test=new int(2);
std::shared_ptr<int> p1=test;    //编译报错,无法从“int *”转换                
                                  //为“std::tr1::shared_ptr<_Ty>”
                                  //因为构造函数被声明为explicit,必须显式调用
std::shared_ptr<int> p2(test);    //正确,显式调用

2) 对全部三种智能指针都应避免的一点:

string vacation("hello,world"); 
shared_ptr<string> pvac(&vacation); // 错误,No pvac过期时,程序将把delete运算符用于非堆内存,导致错误
shared_ptr<string> pvac1(new string("hello,world"));  //正确

下面简要说明四种智能指针的特点:

1)std::auto_ptr:属于独占内存的方式,当p1=p2;时,p2的内存使用权转移给p1(p1指向p2之前所指向的地址),p2成为空悬指针(指针地址为0),若之后使用p2,可以编译通过,但运行时会出现内存访问错误,不安全,会出现内存崩溃的问题。也因此不能被放入容器中(C++11已将其摒弃)

int* test=new int(2);
    std::auto_ptr<int> p1(new int(5));
    printf("p1:%d\n",p1);
    std::auto_ptr<int> p2(test);
    std::auto_ptr<int> p3=p1;    //不报编译错误
    printf("p3:%d\n",p3);
    printf("p1:%d\n",p1);
    //std::auto_ptr<int> p4(p2);   //运行时报访问错误
    //printf("%d\n",*p1);   //报访问错误,因为p1将内存管理权转移给p3了,p1悬空
    printf("%d\n",*p2);

这里写图片描述

2)C++引入的unique_ptr,也属于独享内存所有权,但优于auto_ptr,拷贝构造函数和赋值函数只有声明没有定义,且为私有函数,不能使用。直接赋值会编译出错。需要赋值的时候用std::move

std::unique_ptr<int> p5(new int(8));
    std::unique_ptr<int> p6(test);
    printf("%d\n",*p5);
    printf("%d\n",*p6);
    //std::unique_ptr<int> p7(p5);   //编译报错,库内为private成员,且只声明,未定义
    //std::unique_ptr<int> p8=p6;    //编译报错,库内为private成员,且只声明,未定义
    //printf("%d\n",*p7);
    //printf("%d\n",*p8);
    std::unique_ptr<int> p9=unique_ptr<int>(test);
    printf("%d\n",*p9);
    printf("p9:%d\n",p9);
    p6=std::move(p9);
    printf("p6:%d\n",p6);
    printf("p9:%d\n",p9);
    //printf("%d\n",*p6);    //内存访问出错,因为p6转移了内存所有权

这里写图片描述

3)shared_ptr(boost、C++11)属于共享内存,内部有引用计数机制(实现方式有两种,一种是辅助类,一种是句柄类),对一个内存对象进行引用计数,当删除其中一个指向该内存的指针时,引用计数减1,但并不会释放该内存对象,只有当该内存对象的引用计数减为0时,才会释放该块内存,避免了指针空悬、内存访问错误的情况。

std::shared_ptr<int> p10(new int(10));
    std::shared_ptr<int> p11(test);
    printf("%d\n",*p10);
    printf("%d\n",*p11);
    printf("p11->use_count:%d\n",p11.use_count());
    std::shared_ptr<int> p12(p10);   //编译正常
    std::shared_ptr<int> p13=p11;    //编译正常
    //std::weak_ptr<int> p13=p11;    //编译正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //p11=p13;    //编译正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //std::shared_ptr<int> p13=test;    //编译报错,无法从“int *”转换为“std::tr1::shared_ptr<_Ty>”,因为构造函数被声明为explicit,必须显式调用
    printf("%d\n",*p11);
    printf("%d\n",*p12);
    //printf("%d\n",*p13);
    std::shared_ptr<int> p14=std::move(p10);
    printf("%d\n",*p14);
    p10 = p14;
    printf("%d\n",*p10);    //内存访问出错,因为p10转移了内存所有权
    string vacation("I wandered lonely as a cloud.");
    shared_ptr<string> pvac(&vacation);   // No
    cout<<*pvac;

这里写图片描述

4)weak_ptr(boost、C++11)为shared_ptr设计,弱引用。只对shared_ptr进行引用,但不改变其计数;所以,当被引用的shared_ptr失效后,相应的weak_ptr也会失效。测试代码如下:

std::shared_ptr<int> p10(new int(10));
    std::shared_ptr<int> p11(test);
    printf("%d\n",*p10);
    printf("%d\n",*p11);
    printf("p11->use_count:%d\n",p11.use_count());
    std::shared_ptr<int> p12(p10);   //编译正常
    //std::shared_ptr<int> p13=p11;    //编译正常
    std::weak_ptr<int> p13=p11;    //编译正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //p11=p13;    //编译正常
    printf("p11->use_count:%d\n",p11.use_count());
    printf("p13->use_count:%d\n",p13.use_count());
    printf("p11:%d\n",p11);
    printf("p13:%d\n",p13);
    //std::shared_ptr<int> p13=test;    //编译报错,无法从“int *”转换为“std::tr1::shared_ptr<_Ty>”,因为构造函数被声明为explicit,必须显式调用
    printf("%d\n",*p11);
    printf("%d\n",*p12);
    //printf("%d\n",*p13);
    std::shared_ptr<int> p14=std::move(p10);
    printf("%d\n",*p14);
    p10 = p14;
    printf("%d\n",*p10);    //内存访问出错,因为p10转移了内存所有权
    string vacation("I wandered lonely as a cloud.");
    shared_ptr<string> pvac(&vacation);   // No
    cout<<*pvac;

这里写图片描述

看起来weak_ptr没有什么实质的作用,但实际上weak_ptr 主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr,用于指向子类的boost::shared_ptr,这样基类仅仅观察自己的 boost::weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 boost::shared_ptr 的引用计数,用以降低复杂度,更好的管理对象

3、 智能指针的实现
这里采用上文提到的辅助类的方式,具体代码如下:

//辅助类,用来引用计数
template<class friendclass,class T>
class U_ptr    
{
    friend friendclass;
    T *_p;
    int use_count;
    U_ptr(T *p):_p(p),use_count(1)
    {
        cout<<"U_ptr constructor called!"<<endl;
    }
    ~U_ptr()
    {
        if(use_count==0)
            delete _p;
        cout<<"U_ptr destructor has called!"<<endl;
    }
};

template<class T>
class smartpointer
{
private:
    U_ptr<smartpointer,T>* _ptr;
public:
    smartpointer(T* ptr):_ptr(new U_ptr<smartpointer,T>(ptr))  //构造函数
    {
        cout<<"smartpointer constructor called!"<<endl;
    }
    T& operator *()    //重载*运算符
    {
        return *(_ptr->_p);
    }
    smartpointer& operator=(const smartpointer &sptr)   //重载=运算符
    {
        ++(sptr._ptr->use_count);      //在减少左操作数的使用计数之前使rhs的使用计数加1,是为了防止自身赋值
        if(--_ptr->use_count==0)
            delete _ptr;
        _ptr=sptr._ptr;
        return *this;
    }
    smartpointer(const smartpointer &sptr):_ptr(sptr._ptr)   //拷贝构造函数
    {
        ++_ptr->use_count;
        cout<<"smartpointer copy constructor called!"<<endl;
    }
    int use_count()   //获取引用计数值
    {
        return _ptr->use_count;
    }
    T* get_ptr()    //获取指针地址
    {
        return _ptr->_p;
    }
    ~smartpointer()   //析构函数,计数为0时才释放指针指向的内存
    {
        cout<<_ptr->_p<<" smartpointer deconstructor called!"<<endl;
        cout<<"before destruction use_count is="<<use_count()<<endl;
        if(--_ptr->use_count==0)
            delete _ptr;
    }
};
int test()
{
    smartpointer<int> p1(new int(8));//a should only be use to construct once  
    cout<<"p1.use_count="<<p1.use_count()<<endl;
    cout<<"p1="<<p1.get_ptr()<<endl;
    smartpointer<int> p2(p1);  
    cout<<"p1.use_count="<<p1.use_count()<<endl;
    cout<<"p2.use_count="<<p2.use_count()<<endl;
    cout<<"p1="<<p1.get_ptr()<<endl;
    cout<<"p2="<<p2.get_ptr()<<endl;
    smartpointer<int> p3(p1); 
    cout<<"p1.use_count="<<p1.use_count()<<endl;
    cout<<"p3.use_count="<<p3.use_count()<<endl;
    cout<<"p1="<<p1.get_ptr()<<endl;
    cout<<"p3="<<p3.get_ptr()<<endl;
    smartpointer<int> p4(p3);
    cout<<"p3.use_count="<<p3.use_count()<<endl;
    cout<<"p4.use_count="<<p4.use_count()<<endl;
    cout<<"p3="<<p3.get_ptr()<<endl;
    cout<<"p4="<<p4.get_ptr()<<endl;
    p4 = p4; 
    cout<<"p4.use_count="<<p4.use_count()<<endl;
    p4 = p3;  
    cout<<"p4.use_count="<<p4.use_count()<<endl;
    cout<<"p3.use_count="<<p3.use_count()<<endl;
    cout<<"p4="<<p4.get_ptr()<<endl;
    cout<<"p3="<<p3.get_ptr()<<endl;
    return 0;
}
int main(){
    test(); 
    return 0;
}

执行结果如下:

这里写图片描述

与实际应用STL中的智能指针结果相近

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值