为什么在调用 QXYSeries::replace() 后,当 size() 没有超过 capacity() 时 QVe

Why does the address of QVector change when size() has not surpassed capacity() after calling QXYSeries::replace()?

本文关键字:QVe capacity size 调用 QXYSeries replace 为什么      更新时间:2023-10-16

>我有一个小型测试程序,可以将值插入QVector。QVector 在启动时保留,以分配最少的内存量。我验证容量是否正确,并且 QVector 的大小不超过容量。

我观察到将数据附加到 QVector 不会更改第一个元素的地址。 到目前为止一切都很好。

然后,我使用 QSplineSeries 类的 replace(( 方法将数据从向量复制到图表。调用此方法后,我再次检查 QVector 中第一个元素的地址,令我惊讶的是地址发生了变化!

每次调用此方法时都会发生这种情况,我想了解为什么会发生这种情况。我怀疑可能与Qt容器的隐式共享有关,但我不明白为什么这应该改变这个容器的地址。

示例代码和输出如下所示。

#include <QApplication>
#include <QDebug>
#include <QtCharts/QChart>
#include <QtCharts/QChartView>
#include <QtCharts/QSplineSeries>
#include <memory>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
auto chart = std::make_unique<QtCharts::QChartView>(new QtCharts::QChart());
chart->chart()->addSeries(new QtCharts::QSplineSeries);
QVector<QPointF> vector;
vector.reserve(1000);
while (true) {
vector.append(QPointF(0, 0));
qDebug() << "1. Size: " << vector.size() << " Capacity: " << vector.capacity();
qDebug() << "1. Address of first element: " << &vector[0];
auto series = dynamic_cast<QtCharts::QSplineSeries*>(chart->chart()->series().at(0));
series->replace(vector);
qDebug() << "2. Size: " << vector.size() << " Capacity: " << vector.capacity();
qDebug() << "2. Address of first element: " << &vector[0];
}
return a.exec();
}

输出

  1. 尺寸: 1 容量: 1000
  2. 第一个元素的地址:0x222725d61e8
  3. 尺寸: 1 容量: 1000
  4. 第一个元素的地址:0x222725dc0d8
  5. 尺寸: 2 容量: 1000
  6. 第一个元素的地址:0x222725dc0d8
  7. 尺寸: 2 容量: 1000
  8. 第一个元素的地址:0x222725d61e8
  9. 尺寸: 3 容量: 1000
  10. 第一个元素的地址:0x222725d61e8
  11. 尺寸: 3 容量: 1000
  12. 第一个元素的地址:0x222725dc0d8
  13. 尺寸: 4 容量: 1000
  14. 第一个元素的地址:0x222725dc0d8
  15. 尺寸: 4 容量: 1000
  16. 第一个元素的地址:0x222725d61e8
  17. 尺寸: 5 容量: 1000
  18. 第一个元素的地址:0x222725d61e8
  19. 尺寸: 5 容量: 1000
  20. 第一个元素的地址:0x222725dc0d8

链接到QSplineSeries的源代码::replace(QVector(调用这里

编辑

我刚刚意识到QVector的赋值运算符在QSplineSeries::replace中被调用,看起来像这样。这意味着QSplineSeries中的内部向量复制了我传入的QVector,但我仍然不明白为什么我传入的QVector会改变它的地址。

您已经遇到了Qt容器与标准库容器不同的一种方式。具体来说,Qt使用隐式共享和写入时复制。这意味着,当创建容器的副本时,副本和原始副本都指向相同的数据。只有当尝试更改其中一个容器的内容时,才会进行深层复制。(要更改的容器会为其数据获取新地址,即使它是原始容器也是如此。

让我们将其应用于您的示例。首先,您将获得QVector的统计数据。接下来你打电话给QSplineSeries::replace(QVector<QPointF>).此功能的重点是将提供的QVector复制到QSplineSeries的数据中。此副本将在函数返回后继续存在。(该参数还创建向量的副本 - 它不是通过引用传递的 - 但是当函数返回时,该副本将被销毁。因此,当您进入下一行时,vector正在与另一个容器共享数据,特别是chart深处的容器。目前为止,一切都好。

检查vector的大小和容量仍然很好。但是你用operator[].这将返回对向量元素的非常量引用。这意味着您可以写入它。你是否真的写信给它并不重要。该对象无法知道您将如何处理引用,因此它必须为潜在的写入做好准备。由于数据是共享的,因此会触发深层复制。vector中的数据将复制到一个新的内存块,您可以覆盖该内存块而不会影响chart。因此,内存地址会更改。

如果要在不触发深层复制的情况下完成分析,请将&vector[0]更改为&vector.at(0)vector.constData()。它们提供对数据的const限定访问,因此它们不会触发深层复制。

注意:const QVector使用的operator[]版本返回常量引用,因此不会触发深层复制。vector的非const性质是这里的一个因素。