为什么QList没有resize()方法?

Why doesn't QList have a resize() method?

本文关键字:方法 resize QList 没有 为什么      更新时间:2023-10-16

我刚刚注意到QList没有resize方法,而QVector ,例如,有一个。这是为什么呢?有没有等效的功能?

嗯,这是更通用的答案,但我希望你能看到,通过QListQVector为什么不需要手动扩展容器。

QList 使用内部缓冲区来保存指向元素的指针(或者,如果元素小于指针大小,或者元素是共享类之一 - 元素本身),并且实际数据将保留在堆上。

在此期间,删除数据不会减少内部缓冲区(空白空间将通过向左或向右移动元素来填充,在开头和结尾留下空间以供以后插入)。

附加项目,如QVector将在数组的末尾创建额外的新空间,并且由于与QVector不同,真实数据不存储在内部缓冲区中,因此无论项目的大小如何,您都可以在单个指令中创建大量空间(与QVector不同) - 因为您只是将指针添加到索引缓冲区中。

例如,如果您使用的是 32 位系统(每个指针 4 个字节),并且在QList中存储了 50 个项目,并且每个项目的大小为 1MB,则缓冲区的大小需要调整为 50MB QVector,并且QList的内部缓冲区只需要分配 200B 的内存。这是您需要在 QVector 中调用resize()的地方,但QList没有必要,因为分配小块内存没有问题,因为分配 50MB 内存。

但是,这是

有代价的,这意味着您有时想要QVector而不是QList:对于存储在QList中的单个项目,您需要在堆上增加一个分配 - 以保留项目的真实数据(内部缓冲区中的指针指向的数据)。如果要添加比指针大的 10000 个项目(因为,如果指针可以容纳,它将直接存储在内部缓冲区中),则需要 10000 次系统调用才能为堆上的 10000 个项目分配数据。但是,如果您使用的是QVector,并且您调用resize,则可以在单个分配调用中容纳所有项目 - 因此,如果您需要大量插入或追加,请不要使用QList,更喜欢QVector。当然,如果您使用 QList 来存储共享类,则不需要额外的分配,这再次使QList更合适。

因此,在大多数情况下,首选QList,因为它是:

  1. 使用索引访问单个元素,访问项目的速度将比QLinkedList
  2. 插入列表中间只需要移动指针来创建空间,并且比移动实际QVector数据更快。
  3. 无需手动保留或调整空间大小,因为空白空间将被移动到缓冲区的末尾以供以后使用,并且在数组中分配空间非常快,因为元素非常小,它可以分配大量空间而不会杀死您的内存空间。

请勿在以下情况下使用它,最好QVector

  1. 如果需要确保数据存储在顺序内存位置
  2. 如果您很少在随机位置插入数据,但要附加大量数据在结尾或开头,这可能会导致大量不必要的系统调用,并且您仍然需要快速索引。
  3. 如果您正在寻找不会随着时间的推移而增长的简单数组的(共享)替代品。

最后,请注意:QList(和QVector)具有reserve(int alloc)函数,如果alloc大于内部缓冲区的当前大小,将导致QList的内部缓冲区增长。但是,这不会影响QList外部大小size()将始终返回列表中包含的确切元素数)。

我认为

原因是QList不需要元素类型具有默认构造函数。因此,没有任何操作QList创建对象,它只会复制它们。

但是,如果您确实需要调整QList的大小(无论出于何种原因),这里有一个函数可以做到这一点。请注意,它只是一个方便的功能,在编写时并没有考虑到性能

template<class T>
void resizeList(QList<T> & list, int newSize) {
    int diff = newSize - list.size();
    T t;
    if (diff > 0) {
        list.reserve(newSize);
        while (diff--) list.append(t);
    } else if (diff < 0) list.erase(list.end() + diff, list.end());
}

wasle 答案很好,但它会多次添加相同的对象。这是一个实用程序函数,它将为智能指针列表添加不同的对象。

template<class T>
void resizeSmartList(QList<QSharedPointer<T> > & list, int newSize) {
    int diff = newSize - list.size();
    if (diff > 0) {
        list.reserve(diff);
        while (diff>0){
            QSharedPointer<T> t = QSharedPointer<T>(new T);
            list.append(t);
            diff--;
        }
    }else if (diff < 0) list.erase(list.end() + diff, list.end());
}

对于没有智能指针的使用,以下内容会将不同的对象添加到列表中。

template<class T>
void resizeList(QList<T> & list, int newSize) {
    int diff = newSize - list.size();
    if (diff > 0) {
        list.reserve(diff);
        while (diff>0){
            T t = new T;
            list.append(t);
            diff--;
        }
    }else if (diff < 0) list.erase(list.end() + diff, list.end());
}

还要记住,你的对象必须有默认构造函数(在标题中用arg="someValue声明的构造函数"),否则它将失败。

只需使用类似的东西

QList<Smth> myList;
// ... some operations on the list here
myList << QVector<Smth>(desiredNewSize - myList.size()).toList();

从本质上讲,到处都有这些to/from Vector/List/Set()的方法,这使得在必要时以某种手动但琐碎而有效(我相信)的方式调整Qt容器的大小变得微不足道。

另一个(1 或 2 行)解决方案是:

myList.reserve(newListSize); // note, how we have to reserve manually
std::fill_n(std::back_inserter(myList), desiredNewSize - myList.size(), Smth());

——这是给STL导向的人:)

有关有效QList::resize()可能变得多么复杂的一些背景知识,请参阅:

  • bugreports.qt.io/browse/QTBUG-42732 ,以及
  • codereview.qt-project.org/#/c/100738/1//ALL