美文网首页
C++11 模板元编程 - 类型选择

C++11 模板元编程 - 类型选择

作者: MagicBowen | 来源:发表于2016-09-17 20:22 被阅读303次

上面我们在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模板元编程 - 目录

相关文章

  • C++11 模板元编程 - 类型选择

    上面我们在send的函数实现中创建了一个msg,它的内存是在函数栈空间上临时申请的。一般系统间发送的消息可能会比较...

  • C++11 模板元编程 - 类型校验

    一般情况下一个系统可以发送和接收的消息是确定的。例如前面的例子中,visitor可以发送AccessReq消息,可...

  • C++11 模板元编程 - 类型萃取

    类型萃取(trait)的概念我们前面有介绍过。可以将trait看做是一种静态反射技术,通过trait我们可以自动提...

  • C++11 模板元编程 - 类型操纵

    本文最开始介绍模板元编程的时候说过,模板元编程是写C++框架离不开的技术。本例将通过介绍模板元编程在dates框架...

  • C++11 模板元编程 - 模板的类型参数

    下面是我们熟悉的类模板的例子:一个简单的容器栈,它可以支持不同的类型做元素。 它的用法如下: 对于模板元编程,我们...

  • C++11 模板元编程 - 模板的类型计算

    除了可以计算数值,编译期更具有价值的是类型计算。我们可以将编译期常量和类型都看做是编译期的可计算对象。 我们知道模...

  • C++11 模板元编程 - 元编程

    从本节开始我们将模板元编程当做一门独立的函数式语言来讨论它的方方面面。 所谓元编程,就是指可以产生程序的程序。由于...

  • C++11 模板元编程 - 鸭子类型

    模板为C++提供了鸭子类型(Duck typing)的特性。所谓鸭子类型,指的是代码关注的不是对象的类型本身,而是...

  • C++11 模板元编程 - 模板的非类型参数

    前面的例子中,我们分别使用了类型和模板作为类模板的参数。除此之外,模板还支持非类型模板参数。 如下用数组实现Sta...

  • C++11 模板元编程 - 两阶段的C++语言

    前面我们介绍了C++模板元编程的基础知识。我们将模板元编程的计算对象统一到类型上,引入了元函数的概念。元函数是模板...

网友评论

      本文标题:C++11 模板元编程 - 类型选择

      本文链接:https://www.haomeiwen.com/subject/nggiettx.html