手把手教你实现buffer(三)——接口及自动扩容


接口

前面说到如果不考虑接口的需求,std::vector<uint8_t>完全可以当作buffer使用。但是buffer还是有特定的使用场景,也需要一些基本的,通用的,易用的接口。Buffer类的接口如下。

公有接口

返回数据指针

  1. uint8_t* data()

返回的非const的指针,也就是可以直接获取到Buffer内部内存的地址,并且可以直接读写数据。

其实不应该返回非const指针的接口,因为等于提供了绕过Buffer内容管理机制的接口,可以直接往内存中读写数据,权限太大。这样很容易破坏内存。

但是有些场景,比如需要更改Buffer的中内容,就需要提供这样的接口,所以使用时一点要注意不要破坏内存,比如写了大于容量的数据量等。

  1. const uint8_t* data() const

返回指向内部内存的const指针,只可读,不可写。

大小和容量

size_t size() const
size_t capacity() const
size()是返回Buffer中的数据量,capacity()是返回Buffer的容量。

写入数据

void SetData(constuint8_t*data,size_tsize)
void AppendData(const uint8_t*data,size_t size)

SetData是往Buffer中放入size大小的数据,AppendData是往Buffer中添加size大小的数据,这两个接口也正是std::vector<uint8_t>所缺少的接口。

下标操作符号

uint8_t& operator[](size_t index)
uint8_t operator[](size_t index) const
重载了两种不同形式的下标操作符,前面一个是返回index位置的引用,可以直接更改数据。后面一个返回index位置的值,只能读取值。

这两个下标操作符的实现是通用写法,适用于如下场景

//b是Buffer对象
//调用的返回引用的下标操作符
b[0] = 18;
//调用的是返回值的下标操作符
int i = b[0];

扩容实现

自动扩容对业务代码来说不感知,调用BufferSetDataAppendData写入数据时,如果当前的容量不够,则会再次分配内存,扩大容量。

内存再分配

voidEnsureCapacityWithHeadroom(size_tcapacity, boolextra_headroom)
它是实现内存再分配,代码如下:

void Buffer::EnsureCapacityWithHeadroom(size_t capacity, bool extra_headroom) {
    
    
    assert(IsConsistent());
    if (capacity <= _capacity) {
    
    
        return;
    }

    //扩大capacity
    size_t newCapacity = extra_headroom?std::max(capacity,_capacity + _capacity/2):capacity;
    std::unique_ptr<uint8_t> newData(new uint8_t[newCapacity]);
    memcpy(newData.get(),_data.get(),_size);
    memset(_data.get(),0,_capacity);
    _data = std::move(newData);
    _capacity= newCapacity;
    assert(IsConsistent());

}

逻辑挺简单,形参capacity表示需要的容量,当_capacity不满足时,则会重新分配内存。

在写入数据时判断是否需要扩容

AppendDataSetData接口中会调用EnsureCapacityWithHeadroom会先判断是否需要扩容。

  • voidAppendData(constuint8_t*data,size_tsize)

Buffer尾部添加数据。

void Buffer::AppendData(const uint8_t* data,size_t size) {
    
    
    assert(IsConsistent());
    size_t newSize = _size + size;
    //判断是否需要扩容
    EnsureCapacityWithHeadroom(newSize,true);
    std::memcpy(_data.get()+_size,data,size);
    _size = newSize;
    assert(IsConsistent());
}
  • SetData(constuint8_t*data,size_tsize)

SetData基于AppendData实现,在起始位置写入数据。

void Buffer::SetData(const uint8_t* data,size_t size) {
    
    
    assert(IsConsistent());
    //重置_size
    size_t oldSize = _size;
    //_size赋值为0,将是从起始位置写入数据
    _size = 0;
    //调用AppendData写入数据
    AppendData(data,size);
    if (_size < oldSize) {
    
    
        //size缩小了,多出来的空间被设置为0
        ZeroTrailingData(oldSize - _size);
    }
}

改变Buffer的大小

void SetSize(size_tsize)

改变Buffer的大小,这个接口功能就像vectorreserve接口,改变Buffer的大小和容量。但是size可以缩小,capacity不能缩小。

void Buffer::SetSize(size_t size) {
    
    
    size_t oldSize = _size;
    EnsureCapacityWithHeadroom(size,true);
    _size = size;
    if (_size < oldSize) {
    
    
        ZeroTrailingData(oldSize - _size);
    }
}

示例

下面的示例说明了Buffersizecapacity的变化。

#include <iostream>
#include "buffer.h"
uint8_t kTestData[] = {
    
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,
0xb,0xc,0xd,0xe,0xf};
int main() {
    
    
    //size和capacity都是7
    Buffer buf(kTestData,7);
    std::cout<<buf.size()<<" "<<buf.capacity()<<std::endl;
    //变小,size是3,capacity是7
    buf.SetData(kTestData,3);
    std::cout<<buf.size()<<" "<<buf.capacity()<<std::endl;
    //size变小,capacity还是7
    buf.SetSize(1);
    std::cout<<"size:"<<buf.size()<<",capacity:"<<buf.capacity()<<std::endl;
}

Buffer使用示例

示例演示了Buffer的用法

  1. 通过一个裸指针构造Buffer
  2. 动态改变Buffersizecapacity
  3. Buffer的移动语义
  4. 通过swap交换Buffer对象
  5. Buffer中放入了POD类型
#include <iostream>
#include "buffer.h"

uint8_t kTestData[] = {
    
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,
0xb,0xc,0xd,0xe,0xf};
using namespace base::lib;
int main(){
    
    
    //size和capacity都是7
    Buffer buf(kTestData,7);
    std::cout<<buf.size()<<" "<<buf.capacity()<<std::endl;
    //变小,size是3,capacity是7
    buf.SetData(kTestData,3);
    std::cout<<buf.size()<<" "<<buf.capacity()<<std::endl;
    //size变小,capacity还是7
    buf.SetSize(1);
    std::cout<<"size:"<<buf.size()<<",capacity:"<<buf.capacity()<<std::endl;
    //index 1的值为0
    std::cout<<"index 1:"<<static_cast<int>(buf[1])<<std::endl;


    //变大
    //capcaity将变大(变成15)
    buf.SetData(kTestData,15);
    std::cout<<"size:"<<buf.size()<<",capcaity:"<<buf.capacity()<<std::endl;
    //size变成20,capcaity变成22(capcaity + capcaity/2)
    buf.SetSize(20);
    std::cout<<"size:"<<buf.size()<<",capcaity:"<<buf.capacity()<<std::endl;
    //index 14是14,index 15的值是0
    std::cout<<"index 14:"<<static_cast<int>(buf[14])<<",index 15:"<<static_cast<int>(buf[15])<<std::endl;

    //再缩小,size会变成3,capcaity还是22
    buf.SetData(kTestData,3);
    std::cout<<"size:"<<buf.size()<<",capcaity:"<<buf.capacity()<<std::endl;
    //index 0的值应该是0
    std::cout<<"index 0:"<<static_cast<int>(buf[0])<<std::endl;
    //index 1的值应该是1
    std::cout<<"index 1:"<<static_cast<int>(buf[1])<<std::endl;
    //index 2的值应该是2
    std::cout<<"index 2:"<<static_cast<int>(buf[2])<<std::endl;

    //测试移动构造函数
    Buffer buf1(kTestData,3,40);
    const uint8_t* data = buf1.data();
    Buffer buf2(std::move(buf1));
    //buf2的size为3,buf2的capcaity为40
    std::cout<<"buf2 size:"<<buf2.size()<<",buf2 capacity "<<buf2.capacity()<<std::endl;
    //移动操作本质是指针的移动,所以data与buf2.data()的指向相同
    std::cout<<(buf2.data() == data)<<std::endl;

    //测试移动操作
    Buffer buf11(kTestData,3,40);
    //const uint8_t* data = buf11.data();
    Buffer buf12(kTestData,15);
    buf12 = std::move(buf11);
    std::cout<<"buf12 size:"<<buf12.size()<<",capacity:"<<buf12.capacity()<<std::endl;
    std::cout<<"buf11 is empty:"<<buf11.empty()<<",buf1 size "<<buf1.size()<<",capacity "<<buf1.capacity()<<std::endl;
    std::cout<<"buf11 data is null "<<(buf11.data() == nullptr)<<std::endl;

    //swap
    Buffer buf21(kTestData,3);
    Buffer buf22(kTestData,6,40);
    uint8_t* data21 = buf21.data();
    //uint8_t* data22 = buf22.data();
    std::swap(buf21,buf22);
    std::cout<<"buff21 size "<<buf21.size()<<",capacity:"<<buf21.capacity()<<std::endl;
    std::cout<<"buff21 data "<<(data21 == buf22.data())<<","<<(data21 == buf21.data())<<std::endl;

    //放入结构体
    struct test {
    
    
        int a=18;
        int b=118;
        int c=188;
        char szInfo[10] = {
    
    0};
    };

    test t;
    memcpy(t.szInfo,"mmmmiiii",8);
    
    Buffer TestBuf(reinterpret_cast<uint8_t*>(&t),sizeof(test));
    std::cout<<"test buf size "<<TestBuf.size()<<",capcaity:"<<TestBuf.capacity()<<std::endl;
    test *t1 = reinterpret_cast<test*>(TestBuf.data());
    std::cout<<"a "<<t1->a<<",b "<<t1->b<<",c "<<t1->c<<",info:"<<t1->szInfo<<std::endl;
}

buffer.cpp的完整代码

Buffer定义在buffer.h文件中,在前一篇文章已经贴出,下面是buffer.cpp的代码。

#include "buffer.h"

void Buffer::ZeroTrailingData(size_t count) {
    
    
    assert(IsConsistent());
    memset(_data.get()+_size,0,count);
}

void Buffer::SetData(const uint8_t* data,size_t size) {
    
    
    assert(IsConsistent());
    size_t oldSize = _size;
    _size = 0;
    AppendData(data,size);
    if (_size < oldSize) {
    
    
        ZeroTrailingData(oldSize - _size);
    }
}

void Buffer::AppendData(const uint8_t* data,size_t size) {
    
    
    assert(IsConsistent());
    size_t newSize = _size + size;
    EnsureCapacityWithHeadroom(newSize,true);
    std::memcpy(_data.get()+_size,data,size);
    _size = newSize;
    assert(IsConsistent());
}

void Buffer::SetSize(size_t size) {
    
    
    size_t oldSize = _size;
    EnsureCapacityWithHeadroom(size,true);
    _size = size;
    if (_size < oldSize) {
    
    
        ZeroTrailingData(oldSize - _size);
    }
}

bool Buffer::IsConsistent() const {
    
    
    return ((_data || _capacity == 0)&&(_capacity >= _size));
}

void Buffer::EnsureCapacityWithHeadroom(size_t capacity, bool extra_headroom) {
    
    
    assert(IsConsistent());
    if (capacity <= _capacity) {
    
    
        return;
    }

    //扩大capacity
    size_t newCapacity = extra_headroom?std::max(capacity,_capacity + _capacity/2):capacity;
    std::unique_ptr<uint8_t> newData(new uint8_t[newCapacity]);
    memcpy(newData.get(),_data.get(),_size);
    memset(_data.get(),0,_capacity);
    _data = std::move(newData);
    _capacity= newCapacity;
    assert(IsConsistent());

}

猜你喜欢

转载自blog.csdn.net/mo4776/article/details/126221456