0%

Custom Resource Management for C/C++

在電腦裡面,有很多Resource的存在,除了最常見的Memory之外,還有FILEHANDLE等以指標為形,卻有自己的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還看不懂。有時間在研究吧