最近一直在写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的方式来管理资源。
或许需要显式的手动分配和释放内存的时代早就结束了…