理解C++智能指针

来源:互联网 发布:不常用的英文单词知乎 编辑:程序博客网 时间:2024/06/07 01:03

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中的智能指针结果相近

原创粉丝点击