在電腦裡面,有很多Resource的存在,除了最常見的Memory之外,還有FILE
,HANDLE
等以指標為形,卻有自己的Destroy function的資源。而這種資源該怎麼管理比較好,就是一門學問了。
C語言
翻閱很多Open source的Project之後,發現大部分的作法都類似這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| typedef void(*freefunc)(void *); struct Object { void *obj_; freefunc func_; }; void custom_alloc(Object *obj, void *obj_, void(*freefunc)(void *)) { obj->obj_ = obj_; obj->func_ = freefunc; } void custom_free(Object *obj) { if (obj->obj_) { if (obj->func_) obj->func_(obj->obj_); else free(obj->obj_); } } custom_alloc(&obj, malloc(10), NULL); custom_free(&obj); custom_alloc(&obj2, fopen("log.txt", "rb"), (freefunc)(fclose)); custom_free(&obj2);
|
由於任何指標型態都能跟void 互轉,所以這種作法行得通。
不過由於C語言的Type system很弱,一旦轉成void之後,無法檢查支援先的Type,如果上面的fclose換成free,編譯器不會警告你任何東西,只有在Runtime才會知道這邊會Core dump。
C++98
由於auto_ptr只能管理Memory之類的Resource,所以我們必須要自行打造輪子。
可以學習C++11中的unique_ptr
帶入Destroy function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| template <typename T> class CustomResource { T* obj_; typedef void (*freeFunc)(T *); freeFunc func_; public: CustomResource(T* obj) : obj_(obj), func_(NULL) {} CustomResource(T* obj, freeFunc func) : obj_(obj), func_(func) {} ~CustomResource() { if (func_) func_(obj_); else delete obj_; } }; CustomResource<int> m(new int); CustomResource<FILE> obj(fopen("log.txt", "w"), (void (*)(FILE *))fclose);
|
雖然這樣子可以盡量避免掉呵叫錯誤Destory function的問題,不過如果硬要cast function prototype也是無法避免。
測試了一下C++11對這種錯誤也沒辦法
1 2
| unique_ptr<FILE, int(*)(FILE *)>(fopen("log.txt", "w"), free); // Compile error unique_ptr<FILE, void (*)(void *)>(fopen("log.txt", "w"), free); // Compile success, but the result is incorrect
|
另外一個發法是透過Template跟template specialization來解決
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| template <typename T> class CustomResource { T* obj_; public: CustomResource(T* obj) : obj_(obj) {} ~CustomResource() { delete obj_; } }; template <> class CustomResource<FILE> { FILE* obj_; public: CustomResource(FILE* obj) : obj_(obj) {} ~CustomResource() { if (obj_) fclose(obj_); } }; CustomResource<int> m(new int); CustomResource<FILE> obj(fopen("log.txt", "w"));
|
這樣雖然可以解決,不過美增加一種Resource,就會增加一大堆類似的程式碼,因此,C++中罕見的Template template parameter就可以在此發揮所長。
可以參考Writing a Smart Handle class using template template parameters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| template <typename T> struct destory { static void free(T * obj) { delete obj; } }; template <> struct destory<FILE> { static void free(FILE * obj) { fclose(obj); } }; template <typename T, typename Destroy = destory<T>> class CustomResource { T* obj_; public: CustomResource(T* obj) : obj_(obj) {} ~CustomResource() { if (obj_) Destroy::free(obj_); } };
|
不過就算把fclose換成free也不會有任何警告,這問題還真是難解。
C++11/C++14
在之前提過了,可以透過unique_ptr跟custom destructor來達成同樣的目的。
1
| unique_ptr<FILE, int (*)(FILE *)> obj(fopen("log.txt", "w"), fclose);
|
C++14之後,新增了make_unique
這個函數,我們可以仿造這方式,寫出一個make_resource
的函數。
參考這一篇C++14 and SDL2: Managing Resources寫來的。
1 2 3 4 5 6 7 8 9
| template<typename Creator, typename Destructor, typename... Arguments> auto make_resource(Creator c, Destructor d, Arguments&&... args) { auto r = c(std::forward<Arguments>(args)...); if (!r) { throw std::runtime_error {"Unable to create resource"}; } typedef typename std::decay<decltype(*r)>::type ResourceType; return std::unique_ptr<ResourceType, void(*)(ResourceType*)>(r, d); } auto obj = make_resource(fopen, (void (*)(FILE *))fclose, "log.txt", "w");
|
這段程式碼的auto跟decltype已經有個抵了,部過decay還看不懂。有時間在研究吧