【问题标题】:Why does the address of QVector change when size() has not surpassed capacity() after calling QXYSeries::replace()?为什么调用 QXYSeries::replace() 后 size() 没有超过 capacity() 时 QVector 的地址会发生变化?
【发布时间】:2020-01-15 19:10:35
【问题描述】:

我有一个将值插入 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

Link to the source code of the QSplineSeries::replace(QVector) call here

编辑

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

【问题讨论】:

    标签: c++ qt qt5 qtcharts


    【解决方案1】:

    您遇到了 Qt 的容器与标准库的容器不同的一种方式。具体来说,Qt 使用implicit sharing 和写时复制。这意味着当制作容器的副本时,副本和原始容器都指向相同的数据。只有在尝试更改其中一个容器的内容时​​才会进行深层复制。 (被更改的容器为其数据获取一个新地址,即使它是原始容器。)

    让我们将此应用于您的示例。首先,您会获得 QVector 的统计信息。接下来你打电话给QSplineSeries::replace(QVector&lt;QPointF&gt;)。这个函数的重点是将提供的QVector复制到QSplineSeries的数据中。该副本将在函数返回后继续存在。 (该参数还创建了向量的副本——它不是通过引用传递的——但是当函数返回时,该副本被销毁。)所以当你进入下一行时,vector 正在与另一个容器共享数据,特别是那个在chart 深处。到目前为止,一切顺利。

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

    如果您想在不触发深拷贝的情况下完成分析,请将&amp;vector[0] 更改为&amp;vector.at(0)vector.constData()。这些提供const-qualified 对数据的访问,因此它们不会触发深层复制。

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

    【讨论】:

    • 你是对的。我不知道在调用任何可以触及内部存储数据的非常量方法时进行了深层复制,即使它没有被写入/修改。感谢您清除它!
    猜你喜欢
    • 1970-01-01
    • 2017-02-06
    • 1970-01-01
    • 2021-05-15
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 1970-01-01
    • 2016-01-06
    相关资源
    最近更新 更多