最近一直在寫time-devourer這個項目,我的多態設計的強迫症又犯了,不可避免和元模版打交道了。這篇文章簡單的稍微講一下我遇到的幾個場景。


  • 自動包裝COM對象指針,生命週期結束自動調用Release。要求T必須能調用Release方法,且T必須是IUnknown的子類。

頭一次寫模版元編程給我肘暈了,這就是編譯期編程麼,害怕.

這裡稍微總結一下幾個點,最上面的is_com_interface有兩個模版參數,

第一個是T, 第二個是void, void沒什麼意義,主要用來做特化匹配.

下面是重點,typename T只有一個參數,默認外部調用的都是這個模板,例如COMPtr

std::void_t<條件…> 這個條件如果成立,就會變成void, 就能用上這個模版了

第一個條件:decltype(std::declval().Release())

std::declvar 這個函數返回一個Type的對象, 用來模擬運行時的情況,然後可以模擬調用運行時的方法.

decltype是一個類型提取器,返回值就是一個Type,運行方式sizeof很像,只能在編譯期運行,不能在運行時調用。

如果declval模擬的對象的Release方法調用失敗了,那就沒有返回值,decltype就會報錯,就無法匹配到這個模版了。

第二個條件:std::enable_if_t<std::is_base_of_v<IUnknown, T»

這個簡單一些,判斷T是否是IUnknown的子類,是就enable_if_t 返回 void

不是就enable_if_t 觸發SFINAE,退回上面的模版。

 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的方式來管理資源。

或許需要顯式的手動分配和釋放內存的時代早就結束了…