這次我們看一下智能指針是如何使用策略模式來釋放資源的,同時又是如何擴展功能,管理更多的資源對象類型的。
3、策略模式
策略模式作為一種軟件設計模式,指對象有某個行為,但是在不同的應用場景中,該行為有不同的實現算法。它的意圖是:定義一系列算法,把它們一個個封裝起來,并且使它們可相互替換,算法可獨立于使用它的客戶而變化。
下面是它的結構圖:
組成結構:
—Strategy(抽象策略角色): 策略類,定義算法的公共接口。
—ConcreteStrategy(具體策略角色):具體策略類,實現某種具體算法。
—Context(環境上下文角色):持有一個策略類的引用,最終給客戶端調用。
本質上就是在C++中,一個Strategy基類定義了虛函數(即算法),它的不同ConcreteStrategy派生類重寫了虛函數的實現,而Context類中有一個Strategy引用類型的數據成員,可以存放不同的派生類對象,Context調用基類數據成員的虛函數時,并不知道數據成員的實際類型,顯然這是面向對象動態綁定機制。因為是Context只依賴于Strategy基類類型,根據里氏替換原則,各個繼承于Strategy的派生類ConcreteStrategy對象可以互相替換,不會對Context有任何影響。
我們看一下智能指針的刪除器,它的功能是為智能指針提供釋放資源的方法,有不同的形式和類型,智能指針會在它的reset()成員函數和析構函數中調用刪除器,也不關心是什么形式的刪除器,只要能調用它的void operator()(T *ptr)
操作符就行。
我們再次腦洞大開一下,把一個提供了形如void operator()(T *ptr)
調用操作符的函數對象類看作是策略基類,它定義了具體策略類所要實現的接口功能。如同上一篇文章中使用面向對象技術把指針封裝成一個類一樣,同樣,現在也把刪除器封裝成一個函數對象類,作為抽象策略基類。如下所示:
class deleter {
public:void operator()(T *ptr);
}
該類對象的核心功能是:void operator()(T *ptr),等同于策略模式中的算法,供智能指針對象調用來銷毀資源對象,只要一個類提供了參數是指針類型沒有返回值的可調用操作符,都可以算作是它的派生類。
顯然,對于普通函數:
void deleter_func(T *ptr) {delete ptr;
}
函數對象:
class deleter_func_obj {
public:void operator()(T *ptr) {delete ptr;}
}
lambda表達式:
auto deleter_lambda = [](T *ptr) {delete ptr};
以及function對象:
function<void(T *ptr)> func = bind(xxxx);
它們都提供了符合要求的可調用對象的函數接口,用面向對象術語的話,可以說它們都是deleter類的派生類,它們都被看作是具體的策略類對象,提供了不同的算法。那么在創建unique_ptr或者shared_ptr對象時,均可以選擇一個作為參數傳入,它們是可以互相替換的。顯然,這正是策略模式背后所體現的思想,可以讓智能指針靈活地支持多種類型的刪除器,在這里,智能指針unique_ptr和shared_ptr對應了context環境角色,而刪除器deleter對應了stragety策略角色。
這種模式的好處:
首先,可以使用不同形式的刪除器。
顯然,可以使用函數指針、函數對象、lambda表達式,以及function對象等不同形式來創建deleter對象,不管它們的外在形式如何,只要實現了void (T *ptr)方法,都可以在創建智能指針時選擇一個作為策略對象傳入,在需要的時候調用它們。
其次,可以擴展智能指針的功能。
策略模式中,可以把context角色類比為一個基類對象,而strategy角色就是派生類對象要實現的虛函數,只不過不是通過繼承基類來實現虛函數功能,而是把要實現的虛函數封裝成strategy對象,然后傳入contex中去回調,顯然這樣也等同于使用不同的stragety擴展了context的不同功能。
我們知道,智能指針可以管理不同形式的資源,資源對象可以是數組,可以是普通對象、可以來自堆中,可以來自系統調用,在釋放時,有的需要使用delete操作符,有的需要使用free()函數,有的需要使用close()函數。比如unique_ptr類,它是C++標準庫提供的類型,無法修改它的源碼,它是怎樣做到能夠管理更多的資源的?
看一下deleter角色,它的接口需要一個參數T *ptr,這個參數就是智能指針對象所管理的資源對象,在調用deleter時由智能指針作為參數傳入ptr。因此,deleter知道ptr的類型信息,完全可以使用ptr做一些具體的算法邏輯操作,這就相當于擴展了智能指針對象的功能:只要deleter對象根據參數類型實現了不同的功能,智能指針又調用deleter,不就是等同于它通過deleter實現了該功能嗎?可以腦補想象一下:智能指針類中有一個虛函數,它通過this指針來訪問ptr數據成員,并實現了相關的功能。
傳入不同的deleter對象,也等同于擴展了智能指針的不同功能。這樣,智能指針不但可以管理內存資源,而且可以管理句柄、socket、文件指針、文件描述符等其它形式的資源。
下面一個演示,通過lambda表達式實現了關閉文件指針的操作,unique_ptr使用它擴展了自己的功能:也可以管理FILE文件指針資源了。
FILE *file = fopen("/tmp/tmp.txt", "r"); // 分配FILE資源
unique_ptr<FILE, void(*)(FILE *)> fup(file, [](FILE *file) {fclose(file);
});