加入星計劃,您可以享受以下權益:

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

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

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

前言

無論是在C還是C++中,指針都是在使用的時候需要非常謹慎的一個點,而在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ù),那么程序就會調用很多次test_func函數(shù),但是由于test_func函數(shù)里沒有delete操作,那么這個時候由new獲得的內存就會一直不能得到釋放,最終導致程序崩潰。

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

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

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

智能指針

所以,這給我們一個啟發(fā),如果將指針和局部變量相聯(lián)系起來,是不是就能解決使用指針所帶來的隱患呢?我們來看下面這樣一個代碼(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í)行就不會導致程序崩潰,因為此時實際上是定義了一個局部變量,在函數(shù)執(zhí)行完畢之后,局部變量也就會自動地釋放掉。

我們繼續(xù)完善代碼,我們在sp類中增加一個拷貝構造函數(shù),增加的代碼如下所示:

class sp
{
private:
    Person *p;

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

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

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

    return 0;
}

我們編譯代碼,編譯結果如下所示:

image-20210228172543467

 

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

sp other = new person();

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

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

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

sp &other2 = tmp;

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

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

而解決方法,也很簡單,那就改成常量引用就好了,因此,我們將拷貝構造函數(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;
}

編譯,運行代碼,結果如下所示:

image-20210228201922544

 

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

image-20210228203637110

 

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

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

class Person
{
private:
    int count;

public:
    void incStrong { count++; }
    void decStrong { count--; }
    void getStrongCount { return count; } /* 因為當前 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的構造函數(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í)行的結果:

image-20210228210842670

 

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

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

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

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

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

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

class sp
{
private:
    Person *p;

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

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

改進

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

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;
}

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

小結

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

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

相關推薦

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

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