本文將介紹智能指針用法的一些平時可能沒注意的細節(關於智能指針的基本用法可以參考前面的博文)。
unique_ptr支持動態數組,而shared_ptr不能直接支持動態數組。std::unique_ptr<int []> ptr(new int[10]);合法,而std::shared_ptr<int []> ptr(new int[10]);是不合法的。
如果通過std::shared_ptr來構造動態數組,則需要顯式指定刪除器,比如下面的代碼:
std::shared_ptr<int> p(new int[10], [](int* p){delete[] p;}); //指定delete[]
也可以用std::default_delete作為刪除器:
std::shared_ptr<int> p(new int[10], std::default_delete<int[]>);
我們可以封裝一個make_shared_array方法讓shared_ptr支持數組:
template<typename T> shared_ptr<T> make_shared_array(size_t size) { return shared_ptr<T>(new T[size], default_delete<T[]>()); }
測試代碼:
std::shared_ptr<int> p = make_shared_array<int>(10); std::shared_ptr<char> p = make_shared_array<char>(10);
unique_ptr缺少一個類似於make_shared的make_unique方法,不過在c++14中會增加make_unique方法。其實要實現一個make_unique方法是比較簡單的:
//支持普通指針 template<class T, class... Args> inline typename enable_if<!is_array<T>::value, unique_ptr<T> >::type make_unique(Args&&... args) { return unique_ptr<T>(new T(std::forward<Args>(args)...)); } //支持動態數組 template<class T> inline typename enable_if<is_array<T>::value && extent<T>::value==0, unique_ptr<T> >::type make_unique(size_t size) { typedef typename remove_extent<T>::type U; return unique_ptr<T>(new U[size]()); } //過濾掉定長數組的情況 template<class T, class... Args> typename enable_if<extent<T>::value != 0, void>::type make_unique(Args&&...) = delete;
實現思路很簡單,如果不是數組則直接創建unique_ptr,如果是數組的話,先判斷是否為定長數組,如果為定長數組則編譯不通過;非定長數組時,獲取數組中的元素類型,再根據入參size創建動態數組的unique_ptr。extent<T>::value用來獲取數組的長度,如果獲取值為0,則不到說明不是定長數組。
unique_ptr指定刪除器和std:: shared_ptr是有差別的,比如下面的寫法:
std:: shared_ptr<int> ptr(new int(1), [](int*p){delete p;}); //正確
std::unique_ptr<int> ptr(new int(1), [](int*p){delete p;}); //錯誤
std::unique_ptr指定刪除器的時候需要確定刪除器的類型,所以不能直接像shared_ptr指定刪除器,可以這樣寫:
std::unique_ptr<int, void(*)(int*)> ptr(new int(1), [](int*p){delete p;});
上面這種寫法在lambda沒有捕獲變量的情況下是正確的,如果捕獲了變量則會編譯報錯:
std::unique_ptr<int, void(*)(int*)> ptr(new int(1), [&](int*p){delete p;}); //錯誤,因為捕獲了變量
為什麼lambda捕獲了變量作為unique_ptr就會報錯呢,因為lambda在沒有捕獲變量的情況下是可以直接轉換為函數指針的,捕獲了就不能轉換為函數指針。
如果希望unique_ptr的刪除器支持lambda,可以這樣寫:
std::unique_ptr<int, std::function<void(int*)>> ptr(new int(1), [&](int*p){delete p;});
我們還可以自定義unique_ptr的刪除器,比如下面的代碼:
#include <memory> #include <functional> using namespace std; struct MyDeleter { void operator()(int*p) { cout<<"delete"<<endl; delete p; } }; int main() { std::unique_ptr<int, MyDeleter> p(new int(1)); return 0; }
智能指針可以很方便的管理當前程序庫動態分配的內存,還可以用來管理第三方庫分配的內存。第三方庫分配的內存一般需要通過第三方庫提供的釋放接口才能釋放,由於第三方庫返回出來的指針一般都是原始指針,如果用完之後沒有調用第三方庫的釋放接口,就很容易造成內存洩露。比如下面的代碼:
void* p = GetHandle()->Create(); //do something… GetHandle()->Release(p);
這段代碼實際上是不安全的,在使用第三方庫分配的內存過程中,可能忘記調用Release接口,還有可能中間不小心返回了,還有可能中間發生了異常,導致無法調用Release接口,這時用智能指針去管理第三方庫的內存就很合適了,只要出了作用域內存就會自動釋放,不用顯式去調用釋放接口了,不用擔心中途返回或者發生異常導致無法調用釋放接口的問題。
void* p = GetHandle()->Create(); std::shared_ptr<void> sp(p, [this](void*p){ GetHandle()->Release(p);});
上面這段代碼就可以保證任何時候都能正確釋放第三方庫分配的內存。雖然能解決問題,但還是有些繁瑣,因為每個第三方庫分配內存的地方都要調用這段代碼,比較繁瑣,我們可以將這段代碼提煉出來作為一個公共函數,簡化調用。
std::shared_ptr<void> Guard(void* p) { return std::shared_ptr<void> sp(p, [this](void*p){ GetHandle()->Release(p);}); } void* p = GetHandle()->Create(); auto sp = Guard(p); //do something…
上面的代碼通過Guard函數做了簡化,用起來比較方便,但仍然不夠安全,因為有可能使用者可能會這樣寫:
void* p = GetHandle()->Create(); Guard(p); //危險,這句結束之後p就被釋放了 //do something…
這樣寫是有問題的,會導致訪問野指針,因為Guard(p);是一個右值,如果不賦值給一個指針的話,Guard(p);這句結束之後,就會釋放,導致p提前釋放了,後面就會訪問野指針的內容。auto sp = Guard(p);需要一個賦值操作,忘記賦值就會導致指針提前釋放,這種寫法仍然不夠安全。我們可以定義一個宏來解決這個問題:
#define GUARD(P) std::shared_ptr<void> p##p(p, [](void*p){ GetHandle()->Release(p);}); void* p = GetHandle()->Create(); GUARD(p); //安全
也可以用unique_ptr來管理第三方的內存:
#define GUARD(P) std::unique_ptr<void, void(*)(int*)> p##p(p, [](void*p){ GetHandle()->Release(p);});
通過宏定義方式我們可以避免智能指針忘記賦值,即方便又安全。
c++11 boost技術交流群:296561497,歡迎大家來交流技術。
可以增加一個帶計數功能的智能指針,將計數器添加到構造函數、復制構造函數和賦值操作符中,這樣,每當一個對象被構造、賦值和復制時,指針計數器都加1,而每當一個失去一個本對象的指針引用時(執行析構函數),計數器都將減1,而且每次執行析構函數時,都檢查計數器值,如果為零,則執行銷毀。這個在C++ Primer(第4版:中文版421-425,英文版494-499)裡有詳細介紹,你可以看看。
不過如果你不主動釋放用new新建的類的話,運行時環境是不會主動釋放它的,造成內存洩漏,直到程序退出才會由操作系統回收其內存。C++不同於Java,C++沒有垃圾回收機制。所以,new和delete對應是個好習慣。
當有幾個對象 共同用一個資源時,則它們同時指向了這個資源,如果用普通的指針,則在其中一個對象析構時就會將這個共用的資源銷毀,有了智能指針就不用擔心,它裡面有對象計數,代表有幾個對象在用它,銷毀一個就減1,直到為0,為0時就表示沒有對象用這個資源了,就會自動釋放資源存儲區!還有很多智能的地方,你自己去網上看