细说智能指针

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说智能指针相关的知识,希望对你有一定的参考价值。

提到指针,我们就会想到指针的高效,当然,滥用指针也会为我们带来许多的潜在bug。
提到指针,我们就会想到内存泄漏。比如,使用指针后忘记释放,久而久之,堆空间就会全部使用完,那么会带来很大的危害。再比如,两个指针指向同一片内存区域,我们对同一片区域进行了多次释放,同样会造成内存泄漏。
为了方便大家的理解,我们先来模拟一下,使用指针却忘记释放带来的危害。首先,我们要定义一个类。这次,还是定义女朋友类吧(之前写过一篇《细说C++的友元》用的就是女朋友类,这次还用这个吧,方便说明问题,更何况我们这群马畜怎么会有女朋友呢,没有女朋友只能自己创建了,哈哈哈哈)。
女生都喜欢自拍,尤其是漂亮的女生。所以,女生会有很多照片,对吧。那么,我们创建的这个女朋友类,就让她有照片吧。当然了,你女朋友的照片肯定不会随便给别人的吧,所以要把picutre这个变量声明为private类型。既然,女生喜欢自拍,并且发朋友圈,也就是说,其他人虽然得不到她的照片,却可以通过她的朋友圈看到她的自拍,也就意味着我们可以通过一个public函数访问picture这个变量。那么,我们现在来写代码。

class Girlfriend{
private:
    int pictures;
public:
    Girlfriend ( int i ){
        cout << "Girlfriend ( int i ) " << endl;   /*这句代码是不需要的,
        写在这里只是为了后期能够方便我们观察
        */
        this->pictures = i;
    }
    int getPic ( void ){
        return this->pictures;
    }
    ~Girlfriend (){
        cout << "~Girlfriend() " << endl;  /*这句代码是不需要的,
        写在这里只是方便我们观察
        */
    }
};

接着,我们来写一个主函数,来使用这个女朋友类。

int main ( int argc, char** argv ){
    Girlfriend* Alice = new Girlfriend( 100 );
    cout << "my girlfriend‘s pictures are " << mp->getPic() << endl;
    system ( "pause" );
    return 0;
}

运行结果:
技术分享图片
我们在主函数中做了什么事情呢?我们通过指针动态创建了一个对象,并且,我创建的这个女朋友Alice有100张照片。看到这里,很多人就会想,指针危险吗?好像没什么危险啊。不是使用正常吗,看到程序运行结果,程序不是完美的执行了嘛,好像也没什么。
首先,我们创建的这个指针Alice没有去释放,仅仅创建了一个,危害不大,但是多了之后呢。那比如,我们现在再来写一个主函数。

int main ( int argc, char** argv ){
    Girlfriend* Alice = new Girlfriend( 100 );
    Girlfriend* Lisa = new Girlfriend( 200 );

    cout << "my girlfriend Alice‘s pictures are " << Alice->getPic() << endl;
    cout << "my girlfriend Lisa‘s pictures are " << Lisa->getPic() << endl;
    system ( "pause" );
    return 0;
}

运行结果:
技术分享图片
我们先抛开指针不谈。我们就来谈谈女朋友。假如Alice是你女朋友,你手机里有她的100张照片,后来你们因为一些事情产生了矛盾分手了,于是你交了另外一个女朋友Lisa。有一次,Lisa翻你的手机,发现你竟然有100张前女友照片,她什么感受,估计要疯了,你说后果严不严重,所以,趁你的现女友没发现之前,赶紧把前女友照片给删了,赶紧把前女友照片释放掉,不然,后果……
现在,回归正题,我们使用指针创建对象,一个,两个,都没问题,那如果多了呢?100个,1000个,10000个,那内存还能受的了吗?所以,用完指针之后一定要释放指针。
但是,我们是人啊,不是机器,总会有遗忘的时候,那么我们有没有办法在指针使用完毕后,它自己释放呢?当有了这个需求后,我们就要开始思考解决办法了。很快,我们就有了解决方案:通过类对象模拟指针。指针有两个运算符,一个是->,另一个是星号,只要在类内重载这两个操作符就行了。那么,这个类的成员变量是什么呢?就是一个指针。通过类来模拟指针的,这就是智能指针了。现在,我们先来写一个简陋版的。

class SmartPointer{
private:
    Girlfriend* sp;
public:
    SmartPointer ( Girlfriend* p = NULL ){
        sp = p;
    }
    Girlfriend* operator -> (){
        return sp;
    }
    Girlfriend& operator * (){
        return *sp;
    }
    ~SmartPointer (){
        delete sp;
        cout << "~SmartPointer() " << endl;  /*这句代码完全没必要,
        写在这里只是便于我们观察    
        */
    }
}

我们在这个智能指针类中,重载指针了指针的两个运算符。通过这样做,我们解决了使用完毕指针后自动释放的问题。那么,还有一个问题,如果两个指针指向同一片区域,这样在释放指针时也会造成内存泄漏,因为同一片区域被释放两次。这个问题怎么解决呢?通过重载拷贝构造函数和赋值操作符。我们现在类内实现拷贝构造函数。

SmartPointer ( const SmartPointer& obj ){
    sp = obj.sp;
    const_cast<SmartPointer&>(obj).sp = NULL;
}

我们实现了拷贝构造函数,因为同一片区域只能有一个指针指向,所以,我们在把obj指向的地址赋给sp后,要将obj的sp赋值为NULL。这里用到了一个强制类型转换。
现在,我们要在类内实现重载赋值操作符。

SmartPointer& operator = ( const SmartPointer& obj ){
    if ( this != &obj ){
        delete sp;
        sp = obj.sp;
        obj.sp = NULL;
    }
    return *this;
}

到这里为止,我们就把智能指针类实现完毕了。同时,使用智能指针还可以避免指针比较或加减运算,因为这些运算会造成指针越界带来的bug。好了,我们看一下,运行结果,符不符合我们的预期。
技术分享图片
哇,非常符合。指针使用完毕后完美释放。
下面是完整代码:

#include <iostream>
#include <string>
using namespace std;
class Girlfriend{
private:
    int pictures;
public:
    Girlfriend ( int i ){
        this->pictures = i;
        cout << "Girlfriend ( int i )" << endl;
    }
    int getPic ( void ){
        return this->pictures;
    }
    ~Girlfriend (){
        cout << "~Girlfriend ()" << endl;
    }
};

class SmartPointer{
private:
    Girlfriend* sp;
public:
    SmartPointer ( Girlfriend* p = NULL ){
        sp = p;
        cout << "SmartPointer ( Girlfriend* p = NULL )" << endl;
    }
    Girlfriend* operator -> (){
        return sp;
    }
    Girlfriend& operator * (){
        return *sp;
    }
    SmartPointer ( const SmartPointer& obj ){
        sp = obj.sp;
        const_cast<SmartPointer&>(obj).sp = NULL;
    }
    SmartPointer& operator = ( const SmartPointer& obj ){
        if ( this != &obj ){
            delete sp;
            sp = obj.sp;
            const_cast<SmartPointer&>(obj).sp = NULL;
        }
        return *this;
    }
    bool isnull (){
        return ( sp == NULL );
    }
    ~SmartPointer (){
        cout << "~SmartPointer()" << endl;
        delete sp;
    }
};

int main ( int argc, char** argv ){
    SmartPointer Alice = new Girlfriend( 100 );
    SmartPointer Lisa = new Girlfriend( 200 );

    cout << "my girlfriend Alice‘s pictures are " << Alice->getPic() << endl;
    cout << "my girlfriend Lisa‘s pictures are " << Lisa->getPic() << endl;

    system ( "pause" );
    return 0;
}

后记:
这篇文章写完也是花了我两个小时了,这其中的代码都是经过我自己电脑测试的。虽然写这篇文章只花了我两个小时,但是,我在写这篇文章之前就已经开始在构思了,该如何写,才能做到通俗易懂。看来真的,想写好一篇文章还是比较困难的,毕竟要把自己学会的东西,通过语言文字描述出来,还是不容易的。之前写的一篇《细说C++的友元》上了博客的推荐,这里非常感谢51cto的小编。因为,那篇文章的缘故,我尽心尽力的写了这篇文章。
希望看完的小伙伴能够学到些知识,如果觉得我哪里讲解的有误,也可以在评论中指出,或者哪里有不懂的地方,也可以在评论中留言,如果有空,我们可以探讨下。
同时,感谢51cto的小伙伴们,大佬们花时间耐着性子看完了这篇文章,谢谢,谢谢你们!
最后,当然是希望,看完的小伙伴们,有所收获后点个赞,表示支持。谢谢!

以上是关于细说智能指针的主要内容,如果未能解决你的问题,请参考以下文章

指针辨析:悬垂指针哑指针野指针智能指针

细说专业|人工智能技术服务

2万字 + 50 张图,细说 JVM 内存分布内存对齐压缩指针!

如何设置 vscode 的代码片段,以便在自动完成后自动触发 vscode 的智能感知?

更新:C++ 指针片段

片段中的 EditText 上的空指针异常 [重复]