(原創)智能指針拾遺

本文將介紹智能指針用法的一些平時可能沒注意的細節(關於智能指針的基本用法能夠參考前面的博文)。html

1.unique_ptr和shared_ptr在構造上的一點差別

  unique_ptr支持動態數組,而shared_ptr不能直接支持動態數組。std::unique_ptr<int []> ptr(new int[10]);合法,而std::shared_ptr<int []> ptr(new int[10]);是不合法的。
  若是經過std::shared_ptr來構造動態數組,則須要顯式指定刪除器,好比下面的代碼:c++

  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[]>());
}

測試代碼:this

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方法是比較簡單的:spa

//支持普通指針
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,則不到說明不是定長數組。指針

2.shared_ptr和unique_ptr指定刪除器方式的一點差別

  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;}); //錯誤c++11


  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;
}

3.經過智能指針管理第三方庫分配的內存

  智能指針能夠很方便的管理當前程序庫動態分配的內存,還能夠用來管理第三方庫分配的內存。第三方庫分配的內存通常須要經過第三方庫提供的釋放接口才能釋放,因爲第三方庫返回出來的指針通常都是原始指針,若是用完以後沒有調用第三方庫的釋放接口,就很容易形成內存泄露。好比下面的代碼:

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,歡迎你們來交流技術。

相關文章
相關標籤/搜索