Photo by Alvaro Reyes on Unsplash |
class A { ... }; void foo() { A a = new A; ... if (...) { delete a; return; } ... delete a; return; }
如同上面的例子,因為if condition成立時foo()會提前return,所以必須在其中delete a,以免產生記憶體洩漏。
然而,這樣的撰寫風格其實具有極高的風險性。
舉個例子,假如另外一位工程師A也參與了foo()的開發,但他並沒有注意到foo()中a物件的存在。這時,假如他在foo()中新增了一個可能提前return的區塊,則記憶體洩漏就產生了。
class A { ... }; void foo() { A a = new A; ... if (...) { delete a; return; } ... // 工程師A加入的區塊 if (...) { return; // memory leak!!! } ... delete a; return; }
要解決這問題,一個好的方法便是使用對象來管理raw pointer。
class A { ... }; class SmartPtr { public: explicit SmartPtr(A *a) : a(a) { } ~SmartPtr() { delete a; } private: A *a; }; void foo() { SmartPtr sPtr(new A); ... if (...) return; ... return; }
在上面的程式碼中,我們引入了一個SmartPtr的類別,這個類別內含一個成員變數A *a,而在SmartPtr的解構函式中會去delete a。這表示,每當SmartPtr的物件被摧毀時,它所擁有的a會被自動delete。
我們在回過頭看看foo(),一開始,我們宣告了一個類別為SmartPtr的物件sPtr,並將new A傳入它的建構函式中。由於sPtr是foo()中的一個區域變數,當foo() return時,sPtr會自動被銷毀,聯帶著也自動delete a。如此一來,我們便不用在每個return的地方都去delete a,大大降低了記憶體洩漏的風險。
我們在回過頭看看foo(),一開始,我們宣告了一個類別為SmartPtr的物件sPtr,並將new A傳入它的建構函式中。由於sPtr是foo()中的一個區域變數,當foo() return時,sPtr會自動被銷毀,聯帶著也自動delete a。如此一來,我們便不用在每個return的地方都去delete a,大大降低了記憶體洩漏的風險。
C++標準程序庫如何處理上述議題?
為了應付上述的需求,在C++標準程序庫中,提出了std::auto_ptr來管理raw pointer,用法如下:
class A { ... }; void foo() { std::auto_ptr<A>(new A); ... if (...) return; ... return; }
然而,使用std::auto_ptr有幾個需注意的地方:
- 不能讓兩個以上的auto_ptr指向同一個raw pointer,否則會出現delete兩次的情況(這在C++中屬於undefined behavior)。有鑑於此,auto_ptr如果呼叫了copy constructor或是 copy assignment的話,原來的auto_ptr會變成指向null。此一特性讓auto_ptr無法用於某些STL的容器中(因為這些容器要求其元素必須有正常的copy行為)。
- 由於std::auto_ptr在解構函式中總是做delete,因此無法用std::auto_ptr來管理陣列(欲解決此一問題,可使用C++11所提供的std::unique_ptr)。
void wrong() { std::auto_ptr<A>(new int[10]) // 糟糕的用法; ... if (...) return; ... return; }
void correct() { std::unique_ptr<int[]>(new int[10]) // 使用std::unique_ptr就可解決上面的問題。... if (...) return; ... return; }
應以獨立語句來生成智慧指標
foo(std::shared_ptr<int>(new int), bar());
上面的程式碼看起來很平常,但其實也潛藏了記憶體洩漏的風險。主要的原因是C++標準並未定義一個函式中參數執行的順序。舉個例子,foo()中參數的執行順序可能為:
- new int
- 執行bar()
- 生成shared_ptr
因此,比較安全的寫法是,將生成智慧指標的敘述獨立出來,如下所示:
std::shared_ptr<int> ptr(new int); // 單獨生成智慧指標 foo(ptr, bar());
沒有留言:
張貼留言