Last Regrets

C草的编译期编程:元模版

· sdttttt

最近一直在写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,退回上面的模版。

template <typename T, typename = void>
struct is_com_interface : std::false_type
{
};

template <typename T>
struct is_com_interface<
        T,
        std::void_t<
            decltype(std::declval<T>().Release()),
            std::enable_if_t<std::is_base_of_v<IUnknown, T>>
        >
    > : std::true_type
{
};

template <typename T>
class COMPtr
{
    static_assert(is_com_interface<T>::value, "Type is not a COM object");
    T* com_ptr;

public:
    COMPtr() : com_ptr(nullptr)
    {
    }

    COMPtr(std::nullptr_t) : com_ptr(nullptr)
    {
    }

    COMPtr(T* p) : com_ptr(p)
    {
    }

    ~COMPtr()
    {
        if (com_ptr) com_ptr->Release();
    };

    explicit operator bool() const { return com_ptr != nullptr; }

    T* Get() { return com_ptr; }
    T* operator->() { return com_ptr; }

    T** GetAddressOf()
    {
        if (com_ptr)
        {
            com_ptr->Release();
        }
        com_ptr = nullptr;
        return &com_ptr;
    }
};

写了C++才发现,Rust这个语言本身很多的原语设计都是沿袭了C++的设计。

生命周期,智能指针,这些概念C++本身也有,只是并不强迫你使用。Rust只是做的更加激进罢了。


也许是Win32编程的缘故,我遇到需要显式分配和释放内存的场景很少,如果有也能封装成RAII的方式来管理资源。

或许需要显式的手动分配和释放内存的时代早就结束了…