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

回眸只為那壹抹淺笑 提交于 2021-01-20 08:28:49

问题


I have a small test program that inserts values into a QVector. The QVector is reserved at start to have a minimum amount of memory allocated. I verify that the capacity is correct and that the size of the QVector does not surpass the capacity.

I observe that appending data to the QVector does not change the address of the first element. All good this far.

I then use the replace() method of the QSplineSeries class to copy the data from the vector to the chart. I again check the address of the first element in the QVector after calling this method and to my surprise the address has changed!

This happens on every call to this method and I want to understand why this happens. I suspect maybe there's something related to Qt's container's implicit sharing, but I don't see why this should change the address of this container.

The example code and output is shown below.

#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();
}

Output

  1. Size: 1 Capacity: 1000
  2. Address of first element: 0x222725d61e8
  3. Size: 1 Capacity: 1000
  4. Address of first element: 0x222725dc0d8
  5. Size: 2 Capacity: 1000
  6. Address of first element: 0x222725dc0d8
  7. Size: 2 Capacity: 1000
  8. Address of first element: 0x222725d61e8
  9. Size: 3 Capacity: 1000
  10. Address of first element: 0x222725d61e8
  11. Size: 3 Capacity: 1000
  12. Address of first element: 0x222725dc0d8
  13. Size: 4 Capacity: 1000
  14. Address of first element: 0x222725dc0d8
  15. Size: 4 Capacity: 1000
  16. Address of first element: 0x222725d61e8
  17. Size: 5 Capacity: 1000
  18. Address of first element: 0x222725d61e8
  19. Size: 5 Capacity: 1000
  20. Address of first element: 0x222725dc0d8

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

EDIT

I just realized that the assignment operator of QVector gets called in QSplineSeries::replace, which looks like this. This means that the internal vector in QSplineSeries copies the QVector I pass in, but I still don't see why the QVector I pass in changes it's address.


回答1:


You've encountered one of the ways in which Qt's containers differ from those of the standard library. Specifically, Qt uses implicit sharing and copy-on-write. That means that when a copy of a container is made, both the copy and the original point to the same data. It is only when an attempt is made to change the contents of one of the containers that a deep copy is made. (The container being changed gets a new address for its data, even if it was the original container.)

Let's apply this to your example. First you get stats for your QVector. Next you call QSplineSeries::replace(QVector<QPointF>). The point of this function is to copy the provided QVector into the data of the QSplineSeries. This copy will survive the function's return. (The parameter also creates a copy of the vector – it's not passed by reference – but that copy is destroyed when the function returns.) So when you get to the next line, vector is sharing data with another container, specifically the one deep within chart. So far, so good.

Checking the size and the capacity of vector is still good. But then you use operator[]. This returns a non-constant reference to an element of the vector. That means you can write to it. It doesn't matter whether or not you actually write to it. The object has no way of knowing what you will do with the reference, so it must prepare for a potential write. Since the data is shared, a deep copy is triggered. The data in vector is copied to a new chunk of memory that you can overwrite without affecting chart. Hence the memory address changes.

If you want to accomplish your analysis without triggering a deep copy, change &vector[0] to &vector.at(0) or to vector.constData(). These provide const-qualified access to the data, so they do not trigger a deep copy.

Note: The version of operator[] used by a const QVector returns a constant reference, so that will not trigger a deep copy. The non-const nature of vector is a factor here.



来源:https://stackoverflow.com/questions/57931574/why-does-the-address-of-qvector-change-when-size-has-not-surpassed-capacity

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!