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



    接口

    前面说到如果不考虑接口的需求,std::vector完全可以当作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& operator[](size_t index)
    uint8_t operator[](size_t index) const
    重载了两种不同形式的下标操作符,前面一个是返回index位置的引用,可以直接更改数据。后面一个返回index位置的值,只能读取值。

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

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

    扩容实现

    自动扩容对业务代码来说不感知,调用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());
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    逻辑挺简单,形参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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    改变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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    示例

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

    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Buffer使用示例

    示例演示了Buffer的用法

    1. 通过一个裸指针构造Buffer
    2. 动态改变Buffersizecapacity
    3. Buffer的移动语义
    4. 通过swap交换Buffer对象
    5. Buffer中放入了POD类型
    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    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());
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
  • 相关阅读:
    哪些因素会影响离子交换树脂交换能力?树脂交换能力变差的原因?
    利用西门子低代码实现企业质量管理流程的敏捷性
    仿函数c++
    Linux设备树 02 ———— 内核笔记
    高压 36V 四通道 DMOS 全桥驱动MS3988/3988N
    CountDownLatch、CyclicBarrier实战场景分析(附代码)
    03 【布局之Aspect-Ratio Container Box-Decoration-Break Object-Fit Object-Position】
    设计原则——设计模式
    记录:2022-9-28 岛屿的最大面积 字母异位词分组 雪花算法实现 偏向锁 |锁升级 阻塞线程的方式
    阅读论文Parallel Instance Query Network for Named Entity Recognition
  • 原文地址:https://blog.csdn.net/mo4776/article/details/126221456