常遇到的動(dòng)態(tài)內(nèi)存回收問(wèn)題
在C++的編程過(guò)程中,我們經(jīng)常需要申請(qǐng)一塊動(dòng)態(tài)內(nèi)存,然后當(dāng)用完以后將其釋放。通常而言,我們的代碼是這樣的:
1: void func()
2: {
3: //allocate a dynamic memory
4: int *ptr = new int;
5:
6: //use ptr
7:
8: //release allocated memory
9: delete ptr;
10: ptr = NULL;
11: }
如果這個(gè)函數(shù)func()邏輯比較簡(jiǎn)單,問(wèn)題不大,但是當(dāng)中間的代碼有可能拋出異常時(shí),上面的代碼就會(huì)產(chǎn)生內(nèi)存泄露(memory leak),如下面代碼中第11行和12行將不會(huì)被執(zhí)行。當(dāng)然有碼友會(huì)說(shuō)用try-catch包起來(lái)就可以了,對(duì),沒(méi)錯(cuò),但是代碼中到處的try-catch也挺被人詬病的SAT答案 www.sats686.com
1: void func()
2: {
3: //allocate a dynamic memory
4: int *ptr = new int;
5:
6: throw “error”; //just an example
7:
8: //use ptr
9:
10: //release allocated memory
11: delete ptr;
12: ptr = NULL;
13: }
而且當(dāng)函數(shù)有多個(gè)返回路徑時(shí),需要在每個(gè)return前都要調(diào)用delete去釋放資源,代碼也會(huì)變的不優(yōu)雅了。
1: void func()
2: {
3: //allocate a dynamic memory
4: int *ptr = new int;
5:
6: if (...)
7: {
8: //...a
9:
10: //release allocated memory
11: delete ptr;
12: ptr = NULL;
13: return;
14: } else if (....)
15: {
16: //...b
17:
18: //release allocated memory
19: delete ptr;
20: ptr = NULL;
21: return;
22: }
23:
24: //use ptr
25:
26: //release allocated memory
27: delete ptr;
28: ptr = NULL;
29: }
鑒于此,我們就要想辦法利用C++的一些語(yǔ)言特性,在函數(shù)退棧時(shí)能夠?qū)⒕植可暾?qǐng)的動(dòng)態(tài)內(nèi)存自動(dòng)釋放掉。熟悉C++的碼友們都知道,當(dāng)一個(gè)對(duì)象退出其定義的作用域時(shí),會(huì)自動(dòng)調(diào)用它的析構(gòu)函數(shù)。也就是說(shuō)如果我們?cè)诤瘮?shù)內(nèi)定義一個(gè)局部對(duì)象,在函數(shù)返回前,甚至有異常產(chǎn)生時(shí),這個(gè)局部對(duì)象的析構(gòu)函數(shù)都會(huì)自動(dòng)調(diào)用。如果我們能夠?qū)⑨尫刨Y源的代碼交付給這個(gè)對(duì)象的析構(gòu)函數(shù),我們就可以實(shí)現(xiàn)資源的自動(dòng)回收。這類(lèi)技術(shù),通常被稱(chēng)為RAII (初始化中獲取資源)托福答案
什么是RAII以及幾個(gè)例子
在C++等面向?qū)ο笳Z(yǔ)言中,為了管理局部資源的分配以及釋放(resource allocation and deallocation),實(shí)現(xiàn)異常安全(exception-safe)、避免內(nèi)存泄露等問(wèn)題,C++之父Bjarne Stroustrup發(fā)明了一種叫做”初始化中獲取資源“ (RAII, Resource Acquisition Is Initialization,也可以叫做Scope-Bound Resource Management)的技術(shù)。簡(jiǎn)單來(lái)說(shuō),它的目的就是利用一個(gè)局部對(duì)象,在這個(gè)對(duì)象的構(gòu)造函數(shù)內(nèi)分配資源,然后在其析構(gòu)函數(shù)內(nèi)釋放資源。這樣,當(dāng)這個(gè)局部對(duì)象退出作用域時(shí),它所對(duì)應(yīng)的的資源即可自動(dòng)釋放。在實(shí)現(xiàn)上,它通常有三個(gè)特點(diǎn):
創(chuàng)建一個(gè)特殊類(lèi),在其構(gòu)造函數(shù)初申請(qǐng)資源;
封裝目標(biāo)對(duì)象,將申請(qǐng)資源的目標(biāo)對(duì)象作為這個(gè)特殊類(lèi)的成員變量;
在這個(gè)類(lèi)的析構(gòu)函數(shù)內(nèi),釋放資源。
一個(gè)典型的例子就是標(biāo)準(zhǔn)庫(kù)中提供的模板類(lèi)std::auto_ptr。如在《C++程序設(shè)計(jì)語(yǔ)言》(《The C++ Programming Language, Special Edition》, Bjarne Stroustrup著,裘宗燕譯)中第327頁(yè)所描述的SAT答案
1: template
2: class std::auto_ptr {
3:
4: public:
5: //在構(gòu)造函數(shù)中,獲得目標(biāo)指針的管理權(quán)
6: explicit auto_ptr(X *p = 0) throw() { ptr = p; }
7: //在析構(gòu)函數(shù)中,釋放目標(biāo)指針
8: ~auto_ptr() throw() { delete ptr; }
9:
10: //...
11:
12: //重裝*和->運(yùn)算符,使auto_ptr對(duì)象像目標(biāo)指針ptr一樣使用
13: X& operator*() const throw() { return *ptr; }
14: X* operator->() const throw() { return ptr; }
15:
16: //放棄對(duì)目標(biāo)指針的管理權(quán)
17: X* release() throw() { X* t = ptr; ptr = 0; return t; }
18:
19: private:
20: X *ptr;
21: };
想要使用它,非常簡(jiǎn)單,例如
1: #include
2:
3: void func()
4: {
5: std::auto_ptr p(new int);
6:
7: //use p just like ptr
8:
9: return;
10: }
另一個(gè)例子,是利用GCC中的cleanup attribute。它可以指定一個(gè)函數(shù),在該變量退出作用域時(shí)可以執(zhí)行。例如Wikipedia上提到的宏
1: #define RAII_VARIABLE(vartype,varname,initval,dtor) \
2: void _dtor_ ## varname (vartype * v) { dtor(*v); } \
3: vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)
我們可以這樣使用,例如
1: void example_usage() {
2: RAII_VARIABLE(FILE*, logfile, fopen("logfile.txt", "w+"), fclose);
3: fputs("hello logfile!", logfile);
4: }
還有一個(gè)例子,是在劉未鵬的博客文章”C++11 (及現(xiàn)代C++風(fēng)格)和快速迭代式開(kāi)發(fā)“中的”資源管理“一節(jié)中看到的,他借助C++11的std::function實(shí)現(xiàn)了這一特性。感興趣的碼友可以到他博客內(nèi)閱讀。
筆者采用的方法
對(duì)于new/delete,使用上面提到的std::auto_ptr就可以了,但是對(duì)于new/delete[]一個(gè)動(dòng)態(tài)的一維數(shù)組,甚至二維數(shù)組,auto_ptr就無(wú)能為力了。而且在一些項(xiàng)目中,特別是一些有著悠久歷史的代碼中,還存在著使用malloc, new混用的現(xiàn)象。所以筆者設(shè)計(jì)了一個(gè)auto_free_ptr類(lèi),實(shí)現(xiàn)目標(biāo)資源的自動(dòng)回收。它的實(shí)現(xiàn)比較簡(jiǎn)單,只利用了RAII的第三個(gè)特點(diǎn)——”在類(lèi)的析構(gòu)函數(shù)內(nèi)釋放資源”,但有一個(gè)優(yōu)點(diǎn)是可以在申請(qǐng)堆內(nèi)存代碼前使用托福答案
代碼如下,
1: //auto_free_ptr is only used for automation free memory
2: template
3: class auto_free_ptr
4: {
5: public:
6: typedef enum {invalid, new_one, new_array, alloc_mem} EFLAG;
7: auto_free_ptr() { initialize(); }
8: ~auto_free_ptr(){ free_ptr(); }
9:
10: ///set the pointer needed to automatically free
11: inline void set_ptr(T** new_ptr_address, EFLAG new_eflag)
12: { free_ptr(); p_ptr = new_ptr_address; eflag = new_eflag; }
13:
14: ///give up auto free memory
15: inline void give_up() { initialize(); }
16:
17: protected:
18: inline void initialize() { p_ptr = NULL; eflag = invalid; }
19: inline void free_ptr() throw()
20: {
21: if(!p_ptr || !(*p_ptr)) return;
22:
23: switch(eflag)
24: {
25: case alloc_mem: { free(*p_ptr), (*p_ptr) = NULL, p_ptr = NULL; break; }
26: case new_one: { delete (*p_ptr), (*p_ptr) = NULL, p_ptr = NULL; break; }
27: case new_array: { delete[] (*p_ptr),(*p_ptr) = NULL, p_ptr = NULL; break; }
28: }
29: }
30:
31: protected:
32: T** p_ptr; //!< pointer to the address of the set pointer needed to automatically free
33: EFLAG eflag; //!< the type of allocation
34:
35: private:
36: DISABLE_COPY_AND_ASSIGN(auto_free_ptr);
37: };
為了使用方便,封裝兩個(gè)宏:
1: // auto-free macros are mainly used to free the allocated memory by some local variables in the internal of function-body
2: #define AUTO_FREE_ENABLE( class, ptrName, ptrType ) \
3: auto_free_ptr auto_free_##ptrName; \
4: auto_free_##ptrName.set_ptr(&ptrName,auto_free_ptr::ptrType)
5:
6: #define AUTO_FREE_DISABLE( ptrName ) auto_free_##ptrName.give_up()
使用起來(lái)很簡(jiǎn)單,例如
1: void func(int nLftCnt, int nRhtCnt)
2: {
3: if (!nLftCnt && !nRhtCnt)
4: return;
5:
6: unsigned *pLftHashs = NULL;
7: unsigned *pRhtHashs = NULL;
8:
9: //在申請(qǐng)堆內(nèi)存之前,使用auto_free_ptr
10: AUTO_FREE_ENABLE(unsigned, pLftHashs, new_array);
11: AUTO_FREE_ENABLE(unsigned, pRhtHashs, new_array);
12:
13: //....
14:
15: if (nLftCnt)
16: {
17: pLftHashs = new unsigned[nLftCnt];
18: //...a
19: }
20:
21: if (nRhtCnt)
22: {
23: pRhtHashs = new unsigned[nRhtCnt];
24: //...b
25: }
26:
27: //....
28:
29: if (...)
30: {
31: //因?yàn)橄旅孢@個(gè)函數(shù)可以釋放資源,所以在它前面放棄對(duì)目標(biāo)指針的管理權(quán)
32: AUTO_FREE_DISABLE(pLftHashs);
33: AUTO_FREE_DISABLE(pRhtHashs);
34:
35: //這個(gè)函數(shù)可以釋放資源
36: free_hash_arrays(pLftHashs, pRhtHashs);
37: }
38: }
同樣的,有時(shí)我們需要申請(qǐng)一個(gè)動(dòng)態(tài)二維數(shù)組,所以也實(shí)現(xiàn)一個(gè)對(duì)應(yīng)的auto_free_2D_ptr
1: //auto_free_2D_ptr is only used for automation free memory of 2D array
2: template
3: class auto_free_2D_ptr
4: {
5: public:
6: typedef enum {invalid, new_one, new_array, alloc_mem} EFLAG;
7: auto_free_2D_ptr() { initialize(); }
8: ~auto_free_2D_ptr() { free_ptr(); }
9:
10: ///set the pointer needed to automatically free
11: inline void set_ptr( T** new_ptr_address,EFLAG new_eflag, int new_length_row )
12: { free_ptr(); p_ptr = new_ptr_address; eflag = new_eflag; length_row = new_length_row; }
13:
14: //give up auto free memory
15: inline void give_up() { initialize(); }
16:
17: protected:
18: inline void initialize() { p_ptr = NULL; eflag = invalid; length_row = 0;}
19: inline void free_ptr() throw()
20: {
21: if(!p_ptr || !(*p_ptr)) return;
22:
23: for(int i = 0; i < length_row; i++)
24: {
25: if(!(*p_ptr)[i]) continue;
26: switch(eflag)
27: {
28: case alloc_mem: { free((*p_ptr)[i]); break; }
29: case new_one: { delete (*p_ptr)[i]; break; }
30: case new_array: { delete[] (*p_ptr)[i]; break; }
31: }
32: (*p_ptr)[i] = NULL;
33: }
34: switch(eflag)
35: {
36: case alloc_mem: { free((*p_ptr)); break; }
37: default: { delete[] (*p_ptr); break; }
38: }
39: (*p_ptr) = NULL, p_ptr = NULL;
40: }
41:
42: protected:
43: T** p_ptr; //!< pointer to the address of the set pointer needed to automatically free
44: EFLAG eflag; //!< the type of allocation
45: int length_row; //!< the row length such as ptr[length_row][length_col]
46:
47: private:
48: DISABLE_COPY_AND_ASSIGN(auto_free_2D_ptr);
49: };
50:
51: #define AUTO_FREE_2D_ENABLE( class, ptrName, ptrType, rowNum ) \
52: auto_free_2D_ptr auto_free_##ptrName; \
53: auto_free_##ptrName.set_ptr(&ptrName,auto_free_2D_ptr::ptrType, rowNum)
54:
55: #define AUTO_FREE_2D_DISABLE( ptrName ) AUTO_FREE_DISABLE( ptrName )
下面是個(gè)例子
1: void func(int row, int col)
2: {
3: if (!row && !col)
4: return;
5:
6: int **ptr = new int*[ row ];
7: for( int r = 0; r < row; ++r ) { ptr[r] = new int[ col ];}
8:
9: AUTO_FREE_2D_ENABLE( int, ptr, new_array, row );
10:
11: //....
12: }
到這里就結(jié)束了,有些碼友可能會(huì)說(shuō),何必這么麻煩,boost內(nèi)有很多智能指針供選擇,用share_ptr, scoped_ptr, scoped_array,unique_ptr, auto_ptr 中的一個(gè)不就行了嗎? 沒(méi)錯(cuò)!如果你正在開(kāi)發(fā)的代碼中,允許用boost,并且在相關(guān)程序接口統(tǒng)一都用智能指針來(lái)管理、不會(huì)用到源對(duì)象指針的話(huà),當(dāng)然優(yōu)先選boost,但是當(dāng)你的代碼中由于歷史原因,有些接口不可變更,且new/delete, malloc/free都存在,而且依然需要使用源對(duì)象指針來(lái)完成大部分工作時(shí),不妨試試我設(shè)計(jì)的這個(gè)閹割版的scoped_ptr/scoped_array。總之,根據(jù)自己的實(shí)際情況來(lái)選擇合適的方案,如果標(biāo)準(zhǔn)方案不適用,就自己寫(xiě)一個(gè)托福答案