qt 5中qlist

发布时间 2023-04-30 02:52:26作者: 马肯尼煤牙巴骨

源起

最近在写modbus 封装
modbus中,数据类型只有bool 和 int16
发送float时,需要把float拆成多个int16
接收float时,需要把多个int16拼接成float

写单元测试时,刚开始使用了qlist 所以一直失败,crash
代码是这样的

template<class T> constexpr inline static T getModbusReadedResult(QVector<quint16> star,bool isBigEndian = false)
{
    int i = 1;
    char* b = (char*)&i;
    bool needCvt = !*b ^ isBigEndian; //不一致需要转换
    //QVector<quint16> bytes = lst.toVector();
    T ret;
    if constexpr (sizeof(T) ==8)
    {
        //uint64_t* lstBytes = (uint64_t*) bytes.data();
        uint64_t* bytes = (uint64_t*) star.data(); //bytes = qvector数组地址时正常,如果是qlist时会崩溃
        if (needCvt)
        {
            (*(uint64_t*)&ret) =
                   (*bytes & 0x00000000000000ff) <<56|
                   (*bytes & 0x000000000000ff00) <<40|
                   (*bytes & 0x0000000000ff0000) <<24|
                   (*bytes & 0x00000000ff000000) <<8 |
                   (*bytes & 0x000000ff00000000) >>8 |
                   (*bytes & 0x0000ff0000000000) >>24|
                   (*bytes & 0x00ff000000000000) >>40|
                   (*bytes & 0xff00000000000000) >>56;
        }
... 略

换成vector后好了
于是打算研究一下qlist这个东西

qt 5的qvector 和标准库的vector基本上拥有相同的内存布局和行为,api可能不同,但数据结构一样,所以这里直接pass

qlist 的基本特性 及结构

qt 5 的qlist 是一个折衷的东西
相对于stl的list,它有下标访问的能力,内存在大多数情况下都是连继的
甚至基于下标访问时间复杂度和qvector大多数情况下是一样的 (一开始我以为是像stl dequeue那样局部连续
相对于stl的vector,它不仅能下标访问,也能快速的插入,但它不是链表结构,所以这个快速插入,其实也不是很快,但大多数情况下,比vector快

因为,qlist的结构是大致可以认为是这样
预留空间 + void* 数组 + 预留空间

qlist插入性能

相对于qvector, 它的不仅后面会预先申请内存,前面也会有,这使得它在前面插入 和最后面插入时,时间复杂度都是O(1),空间不足时O(n)
中间插入时,时间复杂度是O(n),但因为它都是void* 所以 O(n) 通常来说会比qvector更快,
如果qvector里放的是指针中间插入的速度它俩一样
如果qvector里放的是小于指针的值,那vector中间插入的速度更快
不过,通常来说,qvector里放的都不是以上两种,所以通常来说qlist中间插入性能会比qvector好,
插入前面的速度 qvector为O(n)弱于qlist
插入后面的速度,两者相当O(1) ,或重分配空间O(n)

qlist 的 两种行为

当 sizeof(T) > sizeof(void)时,或者qlist不认识的类型时, T会被new 在heap, 把指针存在qlist的数组空间中
当 sizeof(T) <= sizeof(void
)时 ,不会把对象new在堆上,而是直接存放在 qlist的数组空间中

为什么我的单元测试使用qlist时会崩溃

因为modbus所需要的数据是 qint16, sizeof(qunit16) 小于sizeof(void), 但qlist的数组空间每一个元素的空间是固定的sizeof(void)
所以有间隙,不再是连续的
于是当我把它当成连续的地址来用时,崩溃了

结论

在qt 5中,qvector应该是首选的容器,如果不需要播放性能的话

慎用qlist, 对于某一些类型,qlist 在32位系统和64位系统中行为不一样
再者,不管是qlist还是qvector,在qt6中都有较大改动,如果要迁移到qt 6,需要注意 旧程序的qvector 可以简单的替换为std::vector ,保证其兼容性
qlist的话,没有类似的东西可以兼顾它的各方面特性, 需要根据使用情景调整选用容器