最近一直在寫time-devourer這個項目,我的多態設計的強迫症又犯了,不可避免和元模版打交道了。這篇文章簡單的稍微講一下我遇到的幾個場景。
- 自動包裝COM對象指針,生命週期結束自動調用Release。要求T必須能調用Release方法,且T必須是IUnknown的子類。
頭一次寫模版元編程給我肘暈了,這就是編譯期編程麼,害怕.
這裡稍微總結一下幾個點,最上面的is_com_interface有兩個模版參數,
第一個是T, 第二個是void, void沒什麼意義,主要用來做特化匹配.
下面是重點,typename T只有一個參數,默認外部調用的都是這個模板,例如COMPtr
std::void_t<條件…> 這個條件如果成立,就會變成void, 就能用上這個模版了
第一個條件:decltype(std::declval
std::declvar
decltype是一個類型提取器,返回值就是一個Type,運行方式sizeof很像,只能在編譯期運行,不能在運行時調用。
如果declval模擬的對象的Release方法調用失敗了,那就沒有返回值,decltype就會報錯,就無法匹配到這個模版了。
第二個條件:std::enable_if_t<std::is_base_of_v<IUnknown, T»
這個簡單一些,判斷T是否是IUnknown的子類,是就enable_if_t
不是就enable_if_t
1template <typename T, typename = void>
2struct is_com_interface : std::false_type
3{
4};
5
6template <typename T>
7struct is_com_interface<
8 T,
9 std::void_t<
10 decltype(std::declval<T>().Release()),
11 std::enable_if_t<std::is_base_of_v<IUnknown, T>>
12 >
13 > : std::true_type
14{
15};
16
17template <typename T>
18class COMPtr
19{
20 static_assert(is_com_interface<T>::value, "Type is not a COM object");
21 T* com_ptr;
22
23public:
24 COMPtr() : com_ptr(nullptr)
25 {
26 }
27
28 COMPtr(std::nullptr_t) : com_ptr(nullptr)
29 {
30 }
31
32 COMPtr(T* p) : com_ptr(p)
33 {
34 }
35
36 ~COMPtr()
37 {
38 if (com_ptr) com_ptr->Release();
39 };
40
41 explicit operator bool() const { return com_ptr != nullptr; }
42
43 T* Get() { return com_ptr; }
44 T* operator->() { return com_ptr; }
45
46 T** GetAddressOf()
47 {
48 if (com_ptr)
49 {
50 com_ptr->Release();
51 }
52 com_ptr = nullptr;
53 return &com_ptr;
54 }
55};
寫了C++才發現,Rust這個語言本身很多的原語設計都是沿襲了C++的設計。
生命週期,智能指針,這些概念C++本身也有,只是並不強迫你使用。Rust只是做的更加激進罷了。
也許是Win32編程的緣故,我遇到需要顯式分配和釋放內存的場景很少,如果有也能封裝成RAII的方式來管理資源。
或許需要顯式的手動分配和釋放內存的時代早就結束了…