前言
無(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