前言
無論是在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