指針操作是C++開發中必備技能。盡管C++11開始引入了智能指針以緩解普通指針的濫用,但是某些場合必須使用普通指針。釋放指針在C/C++編程中非常重要,一般推薦釋放指針后立即將指針設置為null,防止出現低級的野指針問題(只能避免低級別的野指針)同時方便調試。
一、C語言時代
在C語言編程中,我們由於沒有C++模板,函數重載功能,所以一般定義一個統一的宏來用於釋放指針。
// 刪除指針 #define SAFE_DELETE(p) { \ if (NULL != (p)) { \ free((p)); \ (p) = NULL;\ }\ } 二、C++時代
C++相對C語言的改進就是引入了面向對象操作,支持函數重載、類繼承、模板、異常處理等等概念。在C++中,一般用函數模板來操作釋放指針,這樣的好處是可以進行類型檢查。
// 刪除數組template <typename T>inline void safe_delete(T *&target) { if (nullptr != target) { delete target; target = nullptr; }} // 刪除數組指針template <typename T>inline void safe_delete_arr(T *&target) { if (nullptr != target) { delete[] target; target = nullptr; }}
三、void *指針問題
在C、C++ 中,void * 指針可以轉換為任意類型的指針類型,在刪除void*指針時編譯器往往會發出如下警告
warning: deleting 'void*' is undefined [enabled by default]
翻譯:警告:刪除“void *”指針可能引發未知情況(默認打開警告)
永遠記住,在C、C++開發中絕對不能忽視警告,一定要重視警告,最好消除警告。有些警告無關緊要,有些警告卻是bug的根源;刪除void *指針的警告就屬於后面一種情況,可能引起嚴重的bug而且難以發現:
1. 使用delete pointer; 釋放void指針void *,系統會以釋放普通指針(char, short, int, long, long long)的方式來釋放void *指向的內存空間;
2. 如果void *指向一個數組指針,那么由於釋放指針時用了delete pointer從而導致內存泄漏,釋放指針正確做法是delete[] pointer;
3. 如果void *指向一個class類,那么系統由於認為void *指向一個普通的內存空間,所以釋放指針時系統class的析構函數不會調用;
釋放void *的解決方案:將void *轉換為原來類型的指針,然后再調用delete釋放指針,如果原來的指針是數組指針,那么必須使用delete []刪除指向的內存空間。
在C++中我們可以使用模板定義內聯函數:
template <typename T>inline void safe_delete_void_ptr(void *&target) { if (nullptr != target) { T* temp = static_cast<T*>(target); delete temp; temp = nullptr; target = nullptr; }}
調用方法
int *psample = new int(100);safe_delete_void_ptr<int>(psample);
利用模板實例化參數統一簡化過程。
測試代碼
safe_delete_demo.cpp
#include <cstddef>#include <cstdlib>#include <string>#include <iostream> template <typename T>inline void safe_delete(T *&target) { if (nullptr != target) { delete target; target = nullptr; }} template <typename T>inline void safe_delete_void_ptr(void *&target) { if (nullptr != target) { T* temp = static_cast<T*>(target); delete temp; temp = nullptr; target = nullptr; }} class A {public: A(std::string name) { this->name = name; }; virtual ~A() { std::cout<<"base class A's destructor"<<", name: "<<this->name<<std::endl; };public: std::string name;}; class AChild: public A {public: AChild(std::string name, std::string school) : A(name){ this->school = school; }; ~AChild() { std::cout<<"child class AChild's destructor"<<", name: "<<this->name <<", school: "<<this->school<<std::endl; }; public: std::string school;}; int main(int argc, char *argv[]) { // 測試safe_delete釋放普通類指針 std::cout<<"safe_delete pointer of type AChild"<<std::endl; AChild *a1 = new AChild("jacky", "Shenzhen University"); safe_delete(a1); std::cout<<std::endl; // 測試safe_delete釋放void*指針 std::cout<<"safe_delete pointer of type void *"<<std::endl; void *vp = new AChild("Polyn", "Southern University of Science and Technology"); safe_delete(vp); std::cout<<std::endl; // 測試safe_delete_void_ptr釋放模板實例化為基類的void*指針 std::cout<<"safe_delete_void_ptr pointer of type void * ==> A *"<<std::endl; void *vpA = new AChild("Heral", "Renmin University of China"); safe_delete_void_ptr<A>(vpA); std::cout<<std::endl; // 測試safe_delete_void_ptr釋放模板實例化為子類的void*指針 std::cout<<"safe_delete_void_ptr pointer of type void * ==> AChild *"<<std::endl; void *vpAChild = new AChild("pevly", "Southeast University"); safe_delete_void_ptr<AChild>(vpAChild); return 0;}
編譯及運行
$ g++ -std=c++11 safe_delete_demo.cpp
safe_delete_demo.cpp: In instantiation of 'void safe_delete(T*&) [with T = void]':
safe_delete_demo.cpp:59:16: required from here
safe_delete_demo.cpp:9:9: warning: deleting 'void*' is undefined [enabled by default]
delete target;
$ ./a.out
safe_delete pointer of type AChild
child class AChild's destructor, name: jacky, school: Shenzhen University
base class A's destructor, name: jacky
safe_delete pointer of type void *
safe_delete_void_ptr pointer of type void * ==> A *
child class AChild's destructor, name: Heral, school: Renmin University of China
base class A's destructor, name: Heral
safe_delete_void_ptr pointer of type void * ==> AChild *
child class AChild's destructor, name: pevly, school: Southeast University
base class A's destructor, name: pevly
通過測試用例我們可以看出。
1. 使用safe_delete釋放明確的類會自動觸發析構函數(如果析構函數為虛函數,那么先調用子類的析構函數再調用子類的直接基類的析構函數);
2. 使用safe_delete釋放void*指針指向的類時,不會觸發對應類的析構函數;
3. 如果使用safe_delete_void_ptr內聯函數釋放void*指針,那么由於在釋放指針前,函數會將void*指針轉換為特定類型的函數指針,所以最終能夠觸發調用析構函數,並且不影響虛類的釋放行為。
————————————————
