• 正文
    • 前言
    • 智能指針的引入
    • 智能指針
    • 改進
    • 小結(jié)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

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

2021/03/02
154
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

前言

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

智能指針的引入

我們以之前的一個程序為例子,也就是Person類,如下是Person類的代碼:

class Person {

public:

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

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

基于此,我們來編寫一個測試函數(shù):

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

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

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

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

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

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

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

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

智能指針

所以,這給我們一個啟發(fā),如果將指針和局部變量相聯(lián)系起來,是不是就能解決使用指針?biāo)鶐淼碾[患呢?我們來看下面這樣一個代碼(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 實例化的對象能夠訪問到 person 類的成員函數(shù)*/
    {
        return p;
    }
};

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

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

同樣的main函數(shù)不變,在這種情況下,test_func的執(zhí)行就不會導(dǎo)致程序崩潰,因為此時實際上是定義了一個局部變量,在函數(shù)執(zhí)行完畢之后,局部變量也就會自動地釋放掉。

我們繼續(xù)完善代碼,我們在sp類中增加一個拷貝構(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

 

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

sp other = new person();

這條語句實際上可以等同于如下這幾條語句:

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

那為什么會報錯呢?這是因為第三條語句,我們將第三條語句進行以下剖析,第三條語句實際上是相當(dāng)于下面這條語句:

sp &other2 = tmp;

那這條語句是為什么會出錯呢,這是因為tmp當(dāng)前是一個臨時變量,而臨時變量是不能夠賦值給非常量引用的。

臨時變量沒有名字,自然不能夠賦值給非常量引用

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

class sp
{
private:
    Person *p;

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

這樣一來就解決這個問題了。

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

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

    s->printInfo();
}

然后,基于此,我們在主函數(shù)里測試test_func函數(shù),測試代碼如下所示:

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

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

    return 0;
}

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

image-20210228201922544

 

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

image-20210228203637110

 

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

下面我們來編寫代碼,首先是Person類的代碼:

class Person
{
private:
    int count;

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

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

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

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

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

緊接著,我們來編寫sp類的代碼,注意:我們在講述原理的時候,提到了定義一個能夠記錄指向Person類次數(shù)的變量,那么在接下來的代碼中,只要涉及指向Person類的操作的時候,就需要將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;
    }
};

為了更好地觀察代碼的運行,我們增加一些打印信息用于觀察,首先是test_func里的,增加的代碼如下所示:

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

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

然后,我們繼續(xù)來編寫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

 

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

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

少用Person*,而是用sp來替代Person*

對于 Person*來說,有兩種操作:per->XXX或者是(*per).XXX

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

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

class sp
{
private:
    Person *p;

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

另外需要注意的一點就是上述中使用&而不是直接返回值的原因是為了提高效率,因為如果是返回值的話就需要調(diào)用構(gòu)造函數(shù),而如果是返回引用的話就不需要。

改進

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

class RefBase {
private:
    int count;

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

上述就是我們分離出來的類,然后Person類從這個類中繼承而來。

class Person : public RefBase{

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

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

上述是我們對于Person類的一個改進,我們還可以進一步進行改進,回顧sp類,sp 類中所定義的私有成員是Person類的實例化對象,那么如果我想要用sp定義任何類型的對象呢,這個時候,就需要使用到模板的概念,下面是改進后的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;
    }
}

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

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

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

基于上述的改進,我們來編寫主函數(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)于智能指針的改進,當(dāng)然,到目前為止,其還是存在問題的,所存在的問題,將在下一節(jié)進行敘述。

小結(jié)

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

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

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

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