C++11 模板元編程 - 類型選擇


上面我們在send的函數實現中創建了一個msg,它的內存是在函數棧空間上臨時申請的。一般系統間發送的消息可能會比較大,我們知道為了避免棧溢出,函數內不宜直接定義內存占用過大的臨時變量,所以我們設計了一個內存池,專門用于消息的內存分配和回收。

auto* msg = PoolAllocator::alloc<BigMsg>();
build(*msg);
// ...
PoolAllocator::free(msg);

PoolAllocator是一個全局的內存池,它根據接收的Msg類型,計算得到需要的內存塊大小,在內存池中找到合適的內存并以Msg*的類型返回,因此接收內存的指針在定義時可以使用auto關鍵字。PoolAllocatorallocfree函數的原型如下:

struct PoolAllocator
{
    template<typename Msg> static Msg* alloc();

    static void free(void* msg);
};

使用內存池解決了在函數棧上申請過大內存問題,但不是沒有代價的!內存池分配的時候需要在一堆內存塊中查找合適的內存塊,無論如何是沒有在棧上直接分配效率高的。如果系統中同時存在大量的小消息,統一使用內存池分配就顯得非常不高效了!

由于dates是框架,它實現時不能預知客戶發送的消息大小情況,所以我們必須有一種機制來讓代碼根據客戶使用的消息大小自動決定在哪里分配內存。

現在我們先實現一種可以在棧上分配內存的分配器:

template<typename Msg>
struct StackAllocator
{
    Msg* alloc()
    {
        return &msg;
    }

private:
    Msg msg;
};

StackAllocator需要如下使用:

StackAllocator<BigMsg> allocator;
auto* msg = allocator.alloc();
build(*msg);

現在我們就可以根據Msg的大小來做類型選擇,如果消息大則用PoolAllocator,否則用StackAllocator。在做此事之前還遺留一個問題,那就是StackAllocator的使用需要生成對象,而PoolAllocator不用。而且StackAllocator申請的內存會隨著函數退出自動釋放,而PoolAllocator申請的內存必須手動釋放。對于此問題,我們再增加一個中間層,用于統一兩者的使用方式。

template<typename Msg>
struct PoolAllocatorWrapper
{
    Msg* alloc()
    {
        msg = PoolAllocator::alloc<Msg>();
        return msg;
    }

    ~PoolAllocatorWrapper()
    {
        PoolAllocator::free(msg);
        msg = nullptr;
    }

private:
    Msg* msg{nullptr};
};

PoolAllocatorWrapperPoolAllocator進行了封裝,并且使用了RAII(Resource Acquisition Is Initialization)技術,在對象析構時自動釋放內存。如此它的用法就和StackAllocator一致了。

現在如果假定認為小于1024字節的算是小消息,那么我們定義如下MsgAllocator來自動進行分配器的類型選擇:

template<typename Msg>
using MsgAllocator = __if(__bool(sizeof(Msg) < 1024), StackAllocator<Msg>, PoolAllocatorWrapper<Msg>);

現在dates中FakeSystemsend接口的實現可以修改如下了:

template<typename BUILDER> 
void send(const BUILDER& builder)
{
    using Msg = __lambda_para(BUILDER, 0);

    MsgAllocator<Msg> allocator;
    auto msg = allocator.alloc();

    builder(*msg);
    // ...
}

由于我們的StackAllocatorPoolAllocatorWrapper都定義在頭文件中,而且成員函數都很短小,所以編譯器基本都會將其內聯進來,因此這里沒有任何的額外開銷。一切都很酷,不是嗎?!!!!

對于MsgAllocator,我們還可以讓它更通用,將判斷消息大小的值也作為模板參數:

template<typename Msg, int BigSize = 1024>
using MsgAllocator = __if(__bool(sizeof(Msg) < BigSize), StackAllocator<Msg>, PoolAllocatorWrapper<Msg>);

類型校驗

返回 C++11模板元編程 - 目錄

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容