不管你是C++初學者,還是想從C語言轉變?yōu)镃++,你都應該了解C++的動態(tài)內存和智能指針,今天我們就來看一下有關這兩個方面的內容。
本文章內容篇幅較長,且干貨滿滿,感興趣的大家可以收藏+點贊,以后慢慢看!
本文適合C語言轉C++或者學習C++的同學,碼字不易,如果喜歡,希望您能來個三連支持一下博主🤞
一、C語言動態(tài)內存
對于C語言來說,動態(tài)內存的申請是通過關鍵字malloc 來實現(xiàn)的,使用malloc進行動態(tài)內存申請,是在堆區(qū)為當前程序分配一塊內存,為了方便我們對于程序中某些片段的內存使用未知大小的時候,給程序的使用者更大的靈活性,可以從外部來決定對于內存的使用多少。該函數(shù)返回void*的指針,我們首先來看一下malloc的聲明是如何的:
void * __cdecl malloc (
_In_ _CRT_GUARDOVERFLOW size_t _Size
) ;
由以上代碼可以看出,malloc使用了__cdecl的調用約定,返回值是void*,然后形式參數(shù)只有一個,把它命名為_Size,是size_t類型的變量。 在使用malloc申請內存的時候,需要注意以下幾個方面:
1.參數(shù)傳遞:
參數(shù)傳遞可以直接傳遞一個變量、一個sizeof計算出來的值,或者一個直接的數(shù)值,都是可以的,如下圖所示: 2.返回值:
我們在一開始已經說過了malloc的返回值是void類型的指針,并且指向的是這一片內存的首地址,如果我們使用它進行內存申請,如果指針類型不是void ,那肯定需要對返回值進行強制類型轉換,一般轉換的類型和我們的變量類型是息息相關的,如下圖所示: 3.內存申請有效性判斷:
我們上面舉得例子,肯定是有問題的,對于申請內存來說,申請兩個字就至關重要了,既然作為程序,你是去申請,那么系統(tǒng)給不給你內存,那這是不一定的事情,所以申請就有可能成功,有可能失敗,那我們肯定得進行有效性判斷啊,但是怎么判斷呢?這就和返回值有關了,如果申請成功,那就返回指向內存首地址的指針,如果失敗,那么返回NULL。所以判斷如下: 4.內存初始化:
我們使用malloc申請出來的內存里面到底是什么東西呢?它本身有沒有對申請到的內存進行初始化呢,我們可以在調試時查看一下: 使用vs2017來進行調試,申請出來的4字節(jié)內存,沒有進行初始化,其中各個字節(jié)都是’cd’,假如我們暫時不用,或者說用不了全部的申請出來的內存,那我們最好還是將其初始化一下,不管后面使用的時候我們會為它賦什么值,我一般會把它初始化為0。
int * ptr1 = ( int * ) malloc ( 4 ) ;
if ( ptr1 == NULL )
{
//進行對應處理
printf ( "malloc false\n" ) ;
return - 1 ;
}
//初始化申請到的4個字節(jié)為0
memset ( ptr1, 0 , 4 ) ;
5.內存越界:
我們在使用動態(tài)申請到的內存一定要注意內存越界的問題,這是一個隱蔽的錯誤,因為編譯器不會在編譯階段對內存越界的操作報錯,而你對越界之后的內存進行讀寫操作,可能發(fā)生不可預估的錯誤 !下面我們故意寫一個內存越界的小代碼,看一下會發(fā)生什么:
int main ( )
{
int * ptr1 = ( int * ) malloc ( 4 ) ;
if ( ptr1 == NULL )
{
//進行對應處理
printf ( "malloc false\n" ) ;
return - 1 ;
}
//初始化申請到的4個字節(jié)為0
memset ( ptr1, 0 , 4 ) ;
for ( int i = 0 ; i < 3 ; i++ , ptr1++ )
{
printf ( "%d\n" , * ptr1) ;
}
return 0 ;
}
結果分析,本來我們的int*指針解引用之后,是我們初始化的數(shù)字0,但是我們for循環(huán)使指針越界之后,就打印出了如上圖所示的其他數(shù)字。并且這樣的數(shù)組越界訪問不會引起編譯器報錯,還是相當危險的。
6.內存釋放與野指針
內存釋放最大的問題是程序員忘記釋放 ,所以一定要養(yǎng)成良好的編程習慣,釋放內存使用的是free函數(shù),需要傳入參數(shù)就是申請的那塊內存的首地址 ,內存釋放的原因是用malloc申請的內存,在當前程序的堆區(qū),如果使用完這塊內存,而程序員不手動釋放的話,那么這塊內存在程序生存期間就會一直無法再次利用,這樣對于內存利用上來說,肯定是不好的,所以養(yǎng)成良好的習慣,使用完畢之后,對申請到的內存進行釋放: 野指針,顧名思義就是狂野的,“沒有家”的指針,也就是指向的那塊地址可能是無效的,尤其常見發(fā)生在一種情況,就是指針在釋放之后,我們還去訪問它指向的那塊地址,就可能會發(fā)生意想不到的結果,并且這種野指針可能會對我們的程序造成傷害,且無法在最初編譯階段發(fā)現(xiàn)報錯,甚至運行過程中也沒有異常發(fā)生,只是我們得不到期望的結果:
if ( ptr1 != NULL )
{
//如果指針變量不為NULL
free ( ptr1) ;
}
//我們故意去解這個指針的引用
printf ( "%d\n" , * ptr1) ;
打印出來了這樣一個負數(shù),顯然不是我們最初給它初始化的0。所以我們該如何避免這樣的操作呢?等到把指針釋放之后,給它置為NULL,這樣的話,如果對指針進行解引用訪問,就會發(fā)生異常報錯:
//初始化申請到的4個字節(jié)為0
memset ( ptr1, 0 , 4 ) ;
if ( ptr1 != NULL )
{
//如果指針變量不為NULL
free ( ptr1) ;
ptr1 = NULL ; //讓指針指向NULL地址
}
//我們故意去解這個指針的引用 這時候就會發(fā)生異常報錯,因為對空指針解引用了
printf ( "%d\n" , * ptr1) ;
有關C語言中動態(tài)內存申請,我們一般需要注意以上的這些點,而C++是與C語言的內存申請有什么不同,或者說,做了哪些改進呢?接下來讓我們一起看一看。
二、C++動態(tài)內存
對于C++來說,我們使用new關鍵字來為程序申請一塊堆區(qū)的內存,至于動態(tài)申請內存的原因在之前C語言malloc那里就有提到,是為了讓內存的使用更加的合理和靈活。這個申請到的對象所占用的內存空間的生存期是從開始申請到,一直到調用delete顯示釋放之前,不會被自動釋放。
1.參數(shù)傳遞
我們可以在使用new動態(tài)申請內存的時候只申請指向單個對象的指針,也可以申請指向多個對象的指針,只需要用不同的寫法就可以了:
int n = 10 ;
int * ptr1 = new int ; //申請指向一個int類型的指針
int * ptr2 = new int [ 10 ] ; //申請指向10個int類型的指針
int * ptr3 = new int [ n] ; //申請指向n個int類型的指針
2.返回值
對于new來說,如果我們給對象申請內存成功之后,那么也會返回指向對象的指針,如果說申請的是多個對象,那么就會返回第一個對象的首地址,當前指針變量自然就指向那個對應的地址。還有就是new在申請內存的時候,就已經指定了固定的類型 ,如上面的代碼所示,我們申請到的都是int型的指針。
3.內存有效性判斷
如果我們在C++中用new去申請一塊動態(tài)內存,那也是有可能成功,有可能失敗,malloc失敗之后返回的是NULL,那new失敗之后返回的是什么呢?我們又該如何去判斷呢?我們有兩種辦法可以用: 第一種就是利用try catch來捕獲異常的發(fā)生,因為在new申請內存失敗之后,會返回一個bad_alloc的異常,如果catch捕獲到這個異常,則進行處理
try
{
int * ptr1 = new int ; //申請指向一個int類型的指針
}
catch ( bad_alloc)
{
//當new分配內存失敗之后,會拋出bad_alloc的異常代碼
cout << "bad alloc" << endl;
//接下來做其他處理
}
第二種是使用定位new的表達式new (place_address) type這種方式來進行處理:
int * ptr1 = new ( nothrow) int ; //申請指向一個int類型的指針,如果失敗,返回nullptr指針
if ( ptr1 == nullptr )
{
//申請失敗進行處理
cout << "new false" << endl;
}
我們使用這種方式來進行內存申請,如果失敗,則不拋出異常,會返回一個nullptr的指針,nothrow是標準庫定義過的對象。
4.內存初始化
那我們分配到內存之后,初始化操作又是怎么初始化的呢?對于malloc來說,必須先申請出來內存,然后進行初始化,但是new可不是這樣,當然,對于new來說,如果先申請內存,再進行初始化,那也是可行的,并且未初始化之前,對于int型指針來說,里面內容也都是'cd’,這個我們之前在malloc那一塊有過介紹。現(xiàn)在來說點不一樣的,那就是在給對象申請內存的同時進行對象初始化。
int * ptr1 = new int ( 39 ) ; //ptr1指向的對象的值為39
int * ptr2 = new int ( ) ; //ptr2指向的int對象被初始化為0
int * ptr3 = new int [ 5 ] ( ) ; //ptr3指向5個int對象初始化為0
在C++11的新標準中 ,給出了一個花括號列表初始化元素的方法,我們可以用花括號來初始化對象的全部或者部分元素,如果只初始化部分元素,那么剩下的就是按照0來賦值,如果初始化的個數(shù)超出了我們本身申請內存的元素個數(shù),編譯器會報出E0146的錯誤,提醒初始值設定項太多,所以使用這種初始化操作,也可以有效防止初始化元素,指針越界的問題:
int * ptr4 = new int [ 5 ] { 1 , 2 , 3 , 4 , 5 } ; //初始化5個int值為1,2,3,4,5
int * ptr5 = new int [ 5 ] { 1 , 2 } ; //初始化前兩個int值為1,2 其余全是0
//編譯器會報錯 錯誤(活動)E0146初始值設定項值太多
//int *ptr6 = new int[5]{ 1,2,3,4,5,6,7 };
5.內存越界
內存越界是指針的一個非常值得注意的問題,我們以上說的申請內存時給對象初始化可以一定程度上防止初始化指針越界,但是并不能防止訪問時候指針越界的操作,對于指針越界,在malloc那里已經舉了例子,我們要做的就是一定要主要防止指針越界這種情況的發(fā)生,因為一旦發(fā)生,可能會有一些意想不到的代碼錯誤出現(xiàn)。
6.內存釋放與野指針
對于C++的內存釋放,使用的是delete關鍵字,有兩種形式,一種是對單個的對象進行釋放,一種是對數(shù)組進行釋放:
int * ptr1 = new int ( 39 ) ; //ptr1指向的對象的值為39
int * ptr3 = new int [ 5 ] ( ) ; //ptr3指向5個int對象初始化為0
delete ptr1; //釋放單個對象的內存
delete [ ] ptr3; //釋放數(shù)組的內存空間
delete[] ptr3 []表示指針指向一個對象數(shù)組的第一個元素 。當我們寫這么一點測試代碼的時候,也許我們不會在這個問題上出錯,但是當項目變大,代碼量變大,我們可能就會出現(xiàn):忘記使用delete釋放內存,這樣會造成內存空間一直被占用,自然也就造成了內存泄露;使用已經釋放過的內存 ,比如我們上面在malloc中的舉例,此時的指針就是野指針 ,用官方點的話來說叫做空懸指針;還有就是對一個已經申請的內存空間進行重復釋放,已經用過delete了,但是忘記了,在程序中又使用了一次,那么可能就會造成堆的破壞:
int * ptr1 = new int ( 39 ) ; //ptr1指向的對象的值為39
int * ptr3 = new int [ 5 ] ( ) ; //ptr3指向5個int對象初始化為0
delete [ ] ptr3; //釋放數(shù)組的內存空間
delete ptr1; //釋放單個對象的內存
cout << * ptr1 << endl; //野指針
delete ptr1; //重復釋放 引發(fā)異常
所以我們一定要注意內存釋放之后,將指針置為nullptr,防止野指針的發(fā)生。
7.對類對象使用new申請內存
當我們使用new來對一個類對象動態(tài)內存申請時候,會執(zhí)行這個類的構造函數(shù),使用delete釋放內存時候,會執(zhí)行析構函數(shù)。
class MyClass
{
public :
MyClass ( ) ;
~ MyClass ( ) ;
private :
} ;
MyClass :: MyClass ( )
{
cout << "Here is constructor" << endl;
}
MyClass :: ~ MyClass ( )
{
cout << "Here is destructor" << endl;
}
int main ( )
{
MyClass * ptr1 = new MyClass; //申請內存 執(zhí)行無參構造函數(shù)
delete ptr1; //釋放內存,執(zhí)行MyClass的析構
return 0 ;
}
那對于C/C++的動態(tài)內存申請的介紹到這里就告一段落了,有優(yōu)點那就是可以使內存使用更靈活,并且可以在堆區(qū)申請內存來使用等等,但是也有缺點,那就是可能造成一些指針和內存上的問題。那我們可以優(yōu)化這些問題嗎?
三、C++智能指針
對于動態(tài)內存的使用,對我們來說會帶來便捷和程序開發(fā)的靈活性,但是防止內存泄露,防止二次釋放造成堆破壞,防止野指針四處游蕩,這些令我們頭大的問題,依舊困擾著太多的開發(fā)者。C++11新特性 ,智能指針 應運而生!!!智能指針的出現(xiàn),主要是為了讓開發(fā)者更安全也更容易地使用動態(tài)內存,作為新學習C++的選手來說,智能指針一定要好好學習掌握,在以后的開發(fā)中多多使用智能指針,而不是平時普通意義的指針。接下來就來依次介紹一下這些指針和簡單的使用舉例吧!智能指針我覺得最多的還是使用吧,其實用起來和普通的指針沒有什么不同,只不過它們使模板類,然后有一些封裝好的內部接口可以調用罷了,對普通指針的使用操作,在智能指針身上照樣都可以使用。有一些使用時候的注意事項也是我們將要提到的。
1. shared_ptr
shared_ptr需要記住一句話,它可以允許多個指針指向同一個對象。
1.1 使用舉例
我們可以使用如下的方式來聲明一個智能指針,這個指針指向int類型的指針,我把它命名為ptr1,目前這個指針是默認初始化的形式,保存的是一個空指針。
shared_ptr< int > ptr1;
當然我們要為這個指針指向一塊動態(tài)分配的內存,該用什么樣的方式來分配內存呢?調用一個名為make_shared的標準庫函數(shù)。接下來我們來介紹對于指針的內存申請和初始化。
//動態(tài)分配一個新對象,初始化值為39
shared_ptr< int > ptr1 = make_shared< int > ( 39 ) ;
////動態(tài)分配一個新對象,初始化值為0
shared_ptr< int > ptr2 = make_shared< int > ( ) ;
//動態(tài)分配一個新對象,初始化值為39
shared_ptr< int > ptr3 ( new int ( 39 ) ) ;
在以上的代碼中我們看到了三個申請內存和初始化方式,也是我們最常用的三種,可以使用make_shared申請內存并初始化對象,也可以使用new來直接初始化,注意new直接初始化時候的寫法,如果寫成下面這樣,是錯誤的:
//不允許隱式轉換 從int*到shared_ptr
shared_ptr< int > ptr3 = new int ( 39 ) ;
有關shared_ptr最重要的可以說是引用計數(shù)的功能了,這也是為什么命名為shared,因為它可以實現(xiàn)多個指針指向同一個對象。當同類型的shared_ptr發(fā)生拷貝或者賦值操作的時候,會將原指針中的引用計數(shù)增加,當一個shared_ptr中引用計數(shù)變?yōu)?的時候,該對象會被釋放。
//動態(tài)分配一個新對象,初始化值為39 引用計數(shù)為1,因為只有當前使用
shared_ptr< int > ptr1 = make_shared< int > ( 39 ) ;
long usecount = ptr1. use_count ( ) ; //計算當前有多少指針在共享對象
cout << usecount << endl;
//動態(tài)分配一個新對象,初始化值為1 引用計數(shù)為1
shared_ptr< int > ptr2 = make_shared< int > ( 1 ) ;
//ptr2指向ptr1指向的對象,ptr1中的對象引用計數(shù)+1
//ptr2原本指向的對象引用計數(shù)-1,變?yōu)?,所以被銷毀,內存釋放
ptr2 = ptr1;
usecount = ptr1. use_count ( ) ; //計算當前有多少指針在共享對象
cout << usecount << endl;
//ptr3指向ptr1指向的對象,引用計數(shù)+1
shared_ptr< int > ptr3 ( ptr1) ;
usecount = ptr1. use_count ( ) ; //計算當前有多少指針在共享對象
cout << usecount << endl;
上圖是申請內存并且初始化ptr1之后,引用計數(shù)為1。 ptr2被修改為指向ptr1的指針之后,引用計數(shù)增加為2。關于ptr3由于文章篇幅限制,就不截圖了,拷貝復制之后,就會將引用計數(shù)增加為3。
說一下引用計數(shù)吧,引用計數(shù)就是用來控制是否該釋放內存而設計的,如果我們的普通指針,發(fā)生賦值操作之后,如果把源指針進行delete之后,現(xiàn)在指針依然指向那個地址空間,只不過是已經釋放的地址空間,這時候如果進行操作,就會發(fā)生野指針現(xiàn)象,執(zhí)行結果隨機性很大,所以有了引用計數(shù),就可以知道這個對象什么時候需要被銷毀,銷毀的時候,那一定是沒有任何的指針在引用它了 。這個引用計數(shù)的概念,在windows內核對象中也被廣泛使用,原理是相通的。
1.2 使用注意事項
普通指針不能直接隱式轉化為shared_ptr:
//不允許隱式轉換 從int*到shared_ptr
shared_ptr< int > ptr3 = new int ( 39 ) ;
如果我們將一個普通指針綁定到shared_ptr身上,那就最好不要再使用普通指針了,而是使用智能指針,因為它的內存在智能指針銷毀時候被釋放,就使用起來很危險。我們舉例:
//注意事項 內存泄露
int * ptr5 = new int ( 22 ) ;
cout << * ptr5 << endl;
//綁定普通指針,內存管理交給shared_ptr,不能再使用普通指針了
shared_ptr< int > ptr6 ( ptr5) ;
//此時ptr6指向了ptr1的對象,那么原本ptr6指向的對象沒有shared_ptr在引用了
//所以原本的內存就會被銷毀,如果我們再使用,可能就很危險
ptr6 = ptr1;
//我們試著去打印下這塊被shared_ptr銷毀的內存ptr5中看看是什么
cout << * ptr5 << endl;
來看一下兩次打印ptr5里面的內容有何區(qū)別: 和我們預期一樣,ptr5中的內存被釋放,所以不能直接訪問了,因為它的生存已經交給了shared_ptr,ptr5將無法知道自己指向的內存何時被銷毀。 不要使用相同的普通指針來初始化多個智能指針,因為可能會造成多次釋放內存,導致堆破壞,內存奔潰:
//注意事項 內存奔潰
int * ptr5 = new int ( 22 ) ;
//多個智能指針綁定同一個普通指針
shared_ptr< int > ptr6 ( ptr5) ;
shared_ptr< int > ptr7 ( ptr5) ;
//此時ptr6指向了ptr1的對象,那么原本ptr6指向的對象沒有shared_ptr在引用了
//所以原本的內存就會被銷毀,如果我們再使用,可能就很危險
ptr6 = ptr1;
//此時ptr7指向了ptr1的對象,那么原本ptr7指向的對象沒有shared_ptr在引用了
//所以原本的內存 再次被銷毀 導致內存奔潰
ptr7 = ptr1;
2. weak_ptr
weak_ptr是一種弱引用的智能指針,它指向shared_ptr管理的對象,并且不會改變這個對象的引用計數(shù),所以不管weak_ptr存在與否,只要shared_ptr引用變?yōu)?,對象內存還是會被釋放。
2.1 使用舉例
在下面的這段代碼中,我們創(chuàng)建了兩個weak_ptr指針,當我們創(chuàng)建這樣的指針時候,需要使用shared_ptr類型的指針來初始化它,并且這個過程不會增加shared_ptr的引用計數(shù)。但是,會增加Weaks變量的計數(shù),我們在下面的運行圖中可以清楚地看到:
//動態(tài)分配一個新對象,初始化值為39 引用計數(shù)為1,因為只有當前使用
shared_ptr< int > ptr1 = make_shared< int > ( 39 ) ;
//long usecount = ptr1.use_count();//計算當前有多少指針在共享對象
//不會改變ptr1的引用計數(shù)
weak_ptr< int > wptr = ptr1; //不改變引用計數(shù)
weak_ptr< int > wptr2 ( ptr1) ; //不改變引用計數(shù)
對于weak_ptr來說,它不會增加引用計數(shù),那么它也就不能控制shared_ptr的銷毀,所以我們不可以直接對這個指針進行解引用操作:
//不可以對weak_ptr解引用
cout << * wptr << endl; //錯誤
cout << * wptr2 << endl; //錯誤
那我們該怎么來訪問一個weak_ptr指針中的對象呢??用一個接口函數(shù)來返回一個shared_ptr類型的指針:
//動態(tài)分配一個新對象,初始化值為39 引用計數(shù)為1,因為只有當前使用
shared_ptr< int > ptr1 = make_shared< int > ( 39 ) ;
//long usecount = ptr1.use_count();//計算當前有多少指針在共享對象
//不會改變ptr1的引用計數(shù)
weak_ptr< int > wptr = ptr1; //不改變引用計數(shù)
weak_ptr< int > wptr2 ( ptr1) ; //不改變引用計數(shù)
shared_ptr< int > ptr2 = wptr. lock ( ) ;
if ( ptr2)
{
//如果不為空,則訪問weak_ptr指向的對象
//否則,說明對象已經被銷毀,不存在了
cout << * ptr2 << endl;
}
此時,shared_ptr指向的對象并沒有被銷毀,所以可以打印出我們想要的結果: 接下來我們將shared_ptr原本指向的對象引用計數(shù)減少為0:
//動態(tài)分配一個新對象,初始化值為39 引用計數(shù)為1,因為只有當前使用
shared_ptr< int > ptr1 = make_shared< int > ( 39 ) ;
//long usecount = ptr1.use_count();//計算當前有多少指針在共享對象
//不會改變ptr1的引用計數(shù)
weak_ptr< int > wptr = ptr1; //不改變引用計數(shù)
weak_ptr< int > wptr2 ( ptr1) ; //不改變引用計數(shù)
//我讓ptr1重新指向一個對象 也就是讓weak_ptr中指向的對象引用計數(shù)變?yōu)? 被銷毀
ptr1 = make_shared< int > ( 8 ) ;
shared_ptr< int > ptr2 = wptr. lock ( ) ;
if ( ptr2)
{
//如果不為空,則訪問weak_ptr指向的對象
//否則,說明對象已經被銷毀,不存在了
cout << * ptr2 << endl;
}
如果我們將一個weak_ptr指向的對象內存釋放之后,那么使用lock函數(shù)就會返回empty,也就無法執(zhí)行 if 語句中的內容,看一下運行結果: 什么也沒有打印出來,因為沒有進入 if 語句中去。說明此時原對象已經被銷毀。
2.2 使用注意事項
接下來要說的這個,是我們在面試時候會經常被問到的 ,有關shared_ptr和weak_ptr的問題,那就是shared_ptr一定是安全的嗎 ?回答:不是的,因為shared_ptr循環(huán)引用,會造成類的析構無法執(zhí)行,造成內存泄露!那么如何避免這種問題呢?就是用weak_ptr和shared_ptr搭配使用,看一下錯誤示范:
class MyClass ; //類的前置聲明 為了在YourClass中識別
class YourClass
{
public :
YourClass ( ) ;
~ YourClass ( ) ;
shared_ptr< MyClass> ptr; //用來指向MyClass的對象
private :
} ;
YourClass :: YourClass ( )
{
cout << "Here is yourClass constructor" << endl;
}
YourClass :: ~ YourClass ( )
{
cout << "Here is yourClass destructor" << endl;
}
class MyClass
{
public :
MyClass ( ) ;
~ MyClass ( ) ;
shared_ptr< YourClass> ptr; //用來指向YourClass的對象
private :
} ;
MyClass :: MyClass ( )
{
cout << "Here is MyClass constructor" << endl;
}
MyClass :: ~ MyClass ( )
{
cout << "Here is MyClass destructor" << endl;
}
void Sub_1 ( )
{
//pa和pb在這里是兩個局部變量,應該在sub_1函數(shù)中new申請內存執(zhí)行構造
shared_ptr< MyClass> pa ( new MyClass ( ) ) ;
shared_ptr< YourClass> pb ( new YourClass ( ) ) ;
pa-> ptr = pb; //shared_ptr賦值
pb-> ptr = pa; //shared_ptr賦值
//函數(shù)退出,執(zhí)行析構 是我們希望的結果
}
int main ( )
{
Sub_1 ( ) ;
return 0 ;
}
最終執(zhí)行結果: 只執(zhí)行了構造函數(shù),沒有執(zhí)行析構函數(shù),所以問題就出在了智能指針的使用上。因為兩個share_ptr互相指向了對方的類對象,在對方進行析構之前,另一個類對象一直被引用中,所以引用計數(shù)不能減少為0,永遠也不可能釋放,就造成了內存泄露,當前解決辦法就是將其中的一個或者兩個share_ptr改為weak_ptr,這樣就ok了:
3. unique_ptr
3.1 使用舉例
unique_ptr我們從名字來聽,unique就是獨一無二的意思,而在使用的過程中,它也確實是這樣的,unique_ptr獨享它指向的對象,不允許共享,當unique_ptr被銷毀,那它指向的對象肯定也就隨之結束。我們來看一下它的聲明和初始化:
unique_ptr< int > ptr1;
這樣一個寫法就是一個empty的unique_ptr指針,它可以指向一個int的unique_ptr。我們還可以結合new給他初始化:
unique_ptr< int > ptr2 ( new int ( 39 ) ) ; //初始化指針指向值為39的對象
那既然是unique,自然就不支持大家指向同一個對象了,所以以下操作都是不被允許的:
//這些都是錯誤的做法,不被允許
ptr1 = ptr2;
unique_ptr< int > ptr3 ( ptr2) ;
那我們可以對unique_ptr干什么呢?unique_ptr提供的外部接口可以用來將對象所有權轉讓,或者直接放棄對象所有權,釋放內存。
unique_ptr< int > ptr1;
unique_ptr< int > ptr2 ( new int ( 39 ) ) ; //初始化指針指向值為39的對象
//release函數(shù)將當前指針的控制權放棄,返回指針,并且將自身置為nullptr
//reset函數(shù)將自己本身指針的所有權放棄,然后重新掌控傳進來的參數(shù)
ptr1. reset ( ptr2. release ( ) ) ;
3.2 注意事項
使用unique_ptr的時候,注意使用get()這個接口函數(shù),因為它會返回一個指針,指向當前的對象,但是之后對象可能被釋放,指針就會失效,但是如果訪問這個get()的返回指針,那么會發(fā)生一些錯誤:
unique_ptr< int > ptr1;
unique_ptr< int > ptr2 ( new int ( 39 ) ) ; //初始化指針指向值為39的對象
//注意事項
int * ptr3 = ptr2. get ( ) ;
ptr2 = nullptr ; //釋放了之前ptr2中的內存
cout << * ptr3 << endl;
打印出了錯誤結果,因為訪問了被釋放的內存。 還有一個需要注意的點是auto_ptr智能指針,這個智能指針是之前就提出來了,但是C++11出來新的智能指針之后,后來幾乎被啟用,就是因為它會在賦值操作時候,就直接交出對象的訪問控制權,而不是說通過接口函數(shù)什么的: 這樣的弊端就是如果我們調用的接口函數(shù),那我們還可以知道自己明確放棄了對當前對象的控制權,而auto_ptr在一個簡單的賦值操作之后,就會交出控制權,其實是比較危險的。
有關智能指針和動態(tài)內存的知識點先更新到這里,其中的知識太多,大家還是需要在后續(xù)的實踐中慢慢摸索!
吟詩一句:“欲窮千里目,更上一層樓”
如果本文能夠給您帶來幫助,來個一鍵三連支持一下博主歐🤞