加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
    • 前言
    • 智能指針的引入
    • 智能指針
    • 改進(jìn)
    • 小結(jié)
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

適合具備 C 語(yǔ)言基礎(chǔ)的 C++ 教程(十三)

2021/03/02
235
閱讀需 1 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

前言

無(wú)論是在C還是C++中,指針都是在使用的時(shí)候需要非常謹(jǐn)慎的一個(gè)點(diǎn),而在C++中,我們引入一個(gè)智能指針的概念,以此來(lái)規(guī)避在使用指針時(shí)可能出現(xiàn)的問(wèn)題。

智能指針的引入

我們以之前的一個(gè)程序?yàn)槔?,也就?code>Person類,如下是Person類的代碼:

class Person {

public:

    Person() 
    {
        cout <<"Pserson()"<    }

    ~Person()
    {
        cout << "~Person()"<    }
    void printInfo(void)
    {
        cout<<"just a test function"<    }
};

基于此,我們來(lái)編寫一個(gè)測(cè)試函數(shù):

void test_func(void)
{
    Person *p = new Person();
    p->printInfo();
}

可以看到在測(cè)試函數(shù)里,我們定義了一個(gè)指針變量,但是,這里需要注意的是,這個(gè)指針變量并沒(méi)有delete操作,緊接著,我們來(lái)編寫main函數(shù),代碼如下所示:

int main(int argc, char **argv)
{    
    int i;

    for (i = 0; i < 2; i++)
        test_func();
    return 0;
}

這樣的程序存在一個(gè)什么隱患呢?如果在main函數(shù)中的i的最大值是是一個(gè)很大的數(shù),那么程序就會(huì)調(diào)用很多次test_func函數(shù),但是由于test_func函數(shù)里沒(méi)有delete操作,那么這個(gè)時(shí)候由new獲得的內(nèi)存就會(huì)一直不能得到釋放,最終導(dǎo)致程序崩潰。

我們將test_func函數(shù)進(jìn)行一些更改,更改如下所示:

void test_func(void)
{
    Person per;
    per.printInfo();
}

main函數(shù)不變,這個(gè)時(shí)候如下i的最大值是一個(gè)很大的數(shù),那么會(huì)導(dǎo)致程序崩潰么,答案是否定的,因?yàn)樵谶@里,在test_func函數(shù)里定義的是一個(gè)局部變量,局部變量是存放在棧里的,也就是說(shuō)每當(dāng)test_func執(zhí)行完局部變量就會(huì)出棧,其所占用的空間自然也就釋放了。

智能指針

所以,這給我們一個(gè)啟發(fā),如果將指針和局部變量相聯(lián)系起來(lái),是不是就能解決使用指針?biāo)鶐?lái)的隱患呢?我們來(lái)看下面這樣一個(gè)代碼(Person類的代碼不變)

class sp
{
private:
    Person *p;

public:
    sp() : p(0) {}

    sp(Person *other)
    {
        cout << "sp(Person *other)" << endl;
        p = other;
    }

    ~sp()
    {
        cout << "~sp()" << endl;
        if (p)
            delete p;
    }

    Person *operator->()  /* -> 被重載,是為了使得 sp 實(shí)例化的對(duì)象能夠訪問(wèn)到 person 類的成員函數(shù)*/
    {
        return p;
    }
};

基于此,我們來(lái)編寫test_func函數(shù):

void test_func(void)
{
    sp s = new Person();
    s->printInfo();
}

同樣的main函數(shù)不變,在這種情況下,test_func的執(zhí)行就不會(huì)導(dǎo)致程序崩潰,因?yàn)榇藭r(shí)實(shí)際上是定義了一個(gè)局部變量,在函數(shù)執(zhí)行完畢之后,局部變量也就會(huì)自動(dòng)地釋放掉。

我們繼續(xù)完善代碼,我們?cè)?code>sp類中增加一個(gè)拷貝構(gòu)造函數(shù),增加的代碼如下所示:

class sp
{
private:
    Person *p;

public:
    /*省略前面已有的代碼*/
    sp(sp &other)
    {
        cout << "sp(sp &other)" << endl;
        p = other.p
    }
};

在增加了拷貝構(gòu)造函數(shù)的基礎(chǔ)上,我們編寫main函數(shù):

int main(int argc, char** argv)
{
    sp other = new Person();

    return 0;
}

我們編譯代碼,編譯結(jié)果如下所示:

image-20210228172543467

 

上述錯(cuò)誤的提示是說(shuō),不能將非常亮的引用與臨時(shí)變量綁定,到底是什么意思呢,我們來(lái)看下面的分析,我們看主函數(shù)的這條語(yǔ)句:

sp other = new person();

這條語(yǔ)句實(shí)際上可以等同于如下這幾條語(yǔ)句:

Person *p = new Person();
sp tmp(p); ==> sp(Person *p)  /*tmp 表示的是臨時(shí)變量*/
sp other(tmp); ==> sp(sp &other2)  

那為什么會(huì)報(bào)錯(cuò)呢?這是因?yàn)榈谌龡l語(yǔ)句,我們將第三條語(yǔ)句進(jìn)行以下剖析,第三條語(yǔ)句實(shí)際上是相當(dāng)于下面這條語(yǔ)句:

sp &other2 = tmp;

那這條語(yǔ)句是為什么會(huì)出錯(cuò)呢,這是因?yàn)?code>tmp當(dāng)前是一個(gè)臨時(shí)變量,而臨時(shí)變量是不能夠賦值給非常量引用的。

臨時(shí)變量沒(méi)有名字,自然不能夠賦值給非常量引用

而解決方法,也很簡(jiǎn)單,那就改成常量引用就好了,因此,我們將拷貝構(gòu)造函數(shù)改為如下的形式:

class sp
{
private:
    Person *p;

public:
    /*省略前面已有的代碼*/
    sp(const sp &other)
    {
        cout << "sp(sp &other)" << endl;
        p = other.p
    }
};

這樣一來(lái)就解決這個(gè)問(wèn)題了。

我們繼續(xù)更改代碼,將test_func代碼改為如下的形式:

void test_func(sp &other)
{
    sp s = other;

    s->printInfo();
}

然后,基于此,我們?cè)谥骱瘮?shù)里測(cè)試test_func函數(shù),測(cè)試代碼如下所示:

int main(int argc, char **argv)
{
    int i;
    sp other = new Person();

    for (i = 0; i < 2; i++)
        test_func(other);

    return 0;
}

編譯,運(yùn)行代碼,結(jié)果如下所示:

image-20210228201922544

 

上述運(yùn)行的結(jié)果提示是當(dāng)前被釋放了兩次,這是為什么呢?我們來(lái)仔細(xì)分析一下,下面是程序執(zhí)行的一個(gè)流程圖:

image-20210228203637110

 

因此,這也就解釋了上述出錯(cuò)的原因,那么可以采取什么方法來(lái)解決這個(gè)錯(cuò)誤呢?原理也是簡(jiǎn)單的,只要不讓它銷毀兩次就行,那我們采取的方法是,定義一個(gè)變量,這個(gè)變量能夠記錄指向Person對(duì)象的個(gè)數(shù),只有當(dāng)前指向這個(gè)Person對(duì)象的個(gè)數(shù)為0的時(shí)候,才執(zhí)行銷毀操作,否則就不執(zhí)行銷毀操作。

下面我們來(lái)編寫代碼,首先是Person類的代碼:

class Person
{
private:
    int count;

public:
    void incStrong { count++; }
    void decStrong { count--; }
    void getStrongCount { return count; } /* 因?yàn)楫?dāng)前 count 屬于是私有數(shù)據(jù)成員,自然編寫這些訪問(wèn)接口是很有必要了 */

    Person() : count(0)
    {
        cout << "Person()" << endl;
    }

    ~Person()
    {
        cout << "~Person()" << endl;
    }

    void printInfo(void)
    {
        cout << "just a test function" << endl;
    }
};

上述代碼中,我們?cè)?code>Person類中定義了私有數(shù)據(jù)成員,并且定義了其訪問(wèn)的接口,同時(shí),我們?cè)?code>Person的構(gòu)造函數(shù)中,初始化了count變量。

緊接著,我們來(lái)編寫sp類的代碼,注意:我們?cè)谥v述原理的時(shí)候,提到了定義一個(gè)能夠記錄指向Person類次數(shù)的變量,那么在接下來(lái)的代碼中,只要涉及指向Person類的操作的時(shí)候,就需要將count加一,下面是sp類的代碼:

class sp
{
private:
    Person *p;

public:
    sp() : p(0) {}

    sp(Person *other)
    {
        cout << "sp(Person *other)" << endl;
        p = other;
        p->incStrong();
    }

    sp(const sp &other)
    {
        cout << "sp(const sp &other)" << endl;
        p = other.p;
        p->incStrong();
    }

    ~sp()
    {
        cout << "~sp()" << endl;

        if (p)
        {
            p->decStrong();
            if (p->getStrongCount() == 0)
            {
                delete p;
                p = NULL;
            }
        }
    }

    Person* operator->()
    {
        return p;
    }
};

為了更好地觀察代碼的運(yùn)行,我們?cè)黾右恍┐蛴⌒畔⒂糜谟^察,首先是test_func里的,增加的代碼如下所示:

void test_func(sp &other)
{
    sp s = other;

    cout<<"In test_func: "<getStrongCount()<
    s->printInfo(); 
}

然后,我們繼續(xù)來(lái)編寫main函數(shù)里面的代碼:

int main(int argc, char **argv)
{    
    int i;

    sp other = new Person();

    cout<<"Before call test_func: "<getStrongCount()<
    for (i = 0; i < 2; i++)
    {
        test_func(other);
        cout<<"After call test_func: "<getStrongCount()<    }
    return 0;
}

編譯,執(zhí)行,下面是代碼執(zhí)行的結(jié)果:

image-20210228210842670

 

對(duì)照著代碼,我們可以看到Person對(duì)象被指向的次數(shù),而且在更改之后的基礎(chǔ)上運(yùn)行,代碼就沒(méi)有出現(xiàn)錯(cuò)誤了。

現(xiàn)在來(lái)小結(jié)一下,在使用了智能指針之后,在遇到需要定義指針型變量的時(shí)候,我們也更加傾向于使用下面的方式:

少用Person*,而是用sp來(lái)替代Person*

對(duì)于 Person*來(lái)說(shuō),有兩種操作:per->XXX或者是(*per).XXX

那么對(duì)于sp來(lái)說(shuō),也應(yīng)該有這兩種操作:sp->XXX或者是(*sp).XXX

為了實(shí)現(xiàn)(*sp).XXX,那么我們還需要額外補(bǔ)充一點(diǎn),就是關(guān)于*運(yùn)算符的重載,重載的代碼如下:

class sp
{
private:
    Person *p;

public:
    /* 省略相關(guān)代碼 */
    Person& operator*()
    {
        return *p;
    }
};

另外需要注意的一點(diǎn)就是上述中使用&而不是直接返回值的原因是為了提高效率,因?yàn)槿绻欠祷刂档脑捑托枰{(diào)用構(gòu)造函數(shù),而如果是返回引用的話就不需要。

改進(jìn)

那么到目前為止,我們的代碼還能不能再進(jìn)行完善呢?我們來(lái)看Person類的代碼,關(guān)于count相關(guān)的代碼,實(shí)際上只要涉及到構(gòu)造一個(gè)智能指針,那么就會(huì)用的到,而這個(gè)時(shí)候,可以把這部分代碼單獨(dú)分離出來(lái),然后,Person類可以從這個(gè)分離出來(lái)的類繼承,這樣就更加具有普適性,比如,我們?nèi)绻胍獦?gòu)造一個(gè)其他的智能指針,所需要的類就可以從這個(gè)分離出來(lái)的類中繼承。我們來(lái)看具體的代碼:

class RefBase {
private:
    int count;

public:
    RefBase() : count(0) {}
    void incStrong(){ count++; }    
    void decStrong(){ count--; }    
    int getStrongCount(){ return count;}
};

上述就是我們分離出來(lái)的類,然后Person類從這個(gè)類中繼承而來(lái)。

class Person : public RefBase{

public:
    Person() {
        cout <<"Pserson()"<    }

    ~Person()
    {
        cout << "~Person()"<    }
    void printInfo(void)
    {
        cout<<"just a test function"<    }
};

上述是我們對(duì)于Person類的一個(gè)改進(jìn),我們還可以進(jìn)一步進(jìn)行改進(jìn),回顧sp類,sp 類中所定義的私有成員是Person類的實(shí)例化對(duì)象,那么如果我想要用sp定義任何類型的對(duì)象呢,這個(gè)時(shí)候,就需要使用到模板的概念,下面是改進(jìn)后的sp類的模板函數(shù)的代碼:

template
class sp
{
private:
    T *p;
    sp() : p(0) {}

    sp(T *other)
    {
        cout<<"sp(T *other)"<        p = other;
        p->incStrong();
    }

    sp(const sp &other)
    {
        cout<<"sp(const sp &other)"<        p = other.p;
        p->incStrong();
    }

    ~sp()
    {
        cout<<"~sp()"<
        if (p)
        {
            p->decStrong();
            if (p->getStrongCount() == 0)
            {
                delete p;
                p = NULL;
            }
        }
    }

    T *operator->()
    {
        return p;
    }

    T& operator*()
    {
        return *p;
    }
}

實(shí)際上也很簡(jiǎn)單,只是將之前的Person換成了T。更改了sp類,那么也就自然需要更改test_func函數(shù)了,更改之后的代碼如下所示:

template
void test_func(sp &other)
{
    sp s = other;

    cout<<"In test_func: "<getStrongCount()<
    s->printInfo();
}

基于上述的改進(jìn),我們來(lái)編寫主函數(shù),代碼如下所示:

int main(int argc, char** argv)
{
    int i;

    sp other = new Person();

    (*other).printInfo();
    cout<<"Before call test_func: "<getStrongCount()<
    for (i = 0; i < 2; i++)
    {
        test_func(other);
        cout<<"After call test_func: "<getStrongCount()<    }

    return 0;
}

至此,就完成了關(guān)于智能指針的改進(jìn),當(dāng)然,到目前為止,其還是存在問(wèn)題的,所存在的問(wèn)題,將在下一節(jié)進(jìn)行敘述。

小結(jié)

本節(jié)的內(nèi)容就到這里結(jié)束了,所涉及的代碼可以通過(guò)百度云鏈接的方式獲取到:

鏈接:https://pan.baidu.com/s/1LUL6HqekmwguqYO6V1ETqw 
提取碼:vu8p

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

在讀碩士研究生,喜歡鉆研嵌入式相關(guān)技術(shù),熱衷于寫文章分享知識(shí),不定期輸出關(guān)于單片機(jī), RTOS,信號(hào)處理等相關(guān)內(nèi)容