QVector introduction
The QVector class is a template class that provides dynamic arrays.
QVector <T> is a common Qt container class. It stores each of its objects in a continuous memory and can use index numbers to quickly access them. QList <T>, qinilist <T>, and QVarLengthArray <T> provide similar functions. They are used as follows:
L QList is generally used most, which can meet most of our needs. Operations such as prepend () and insert () are usually faster than QVector, because QList stores its objects in different ways (Algorithmic Complexity. In addition, its index-based API is more convenient to use than qinilist's iterator-based API. At last, it has fewer code extensions when executing the program.
L qinilist: when you need to use a real linked list, you need to insert the object into the list within a constant period of time. You want to use the iterator instead of the index number to access the object, in this case, use qinilist!
L QVector: If you want to store your object in continuous memory, your object is larger than the pointer. You want to avoid inserting the object into the heap header separately, use QVector.
L QVarLengthArray: If you want a low-level variable-size container, QVarLengthArray is enough. Its advantage is that it is fast!
The following example uses QVector to store integer and QString values:
QVector <int> integerVector;
QVector <QString> stringVector;
QVector stores the vector container of an object. Generally, the initial size is used to create a vector container. For example, the following code constructs a QVector with 200 elements:
QVector <QString> vector (200 );
If the created vector container object does not have an initial value, it will be initialized by the default constructor of the class using this vector container. Both the basic and pointer types are initialized to 0. If you want to use other initial values to initialize an object, you can add another parameter during initialization:
QVector <QString> vector (200, "Pass ");
You can also call the fill () function to fill the vector container at any time.
Like an array of C ++, the index number of QVector starts from 0. Operator [] ()
If (vector [0] = "Liz ")
Vector [0] = "Elizabeth ";
If you only want to read the object of the vector container, you can call the at () function to access the object.
For (int I = 0; I <vector. size (); ++ I)
{
If (vector. at (I) = "Alfonso ")
Cout <"FoundAlfonso at position" <I <endl;
}
Calling the at () function to read objects is faster than using operator [] (), because deep copy is not used ).
You can call the data () function to access the data stored in QVector. This function returns the pointer to the first object of the vector container. In this way, you can use pointers to access and modify objects in a vector container. You can use a pointer to pass a QVector vector container to a function that receives a common C ++ array.
You can use indexOf () and lastIndexOf () to find the number of times an object appears. The former searches forward from a given position, and the latter searches backward. If they are found, the corresponding index number is returned; otherwise,-1 is returned. For example:
int i = vector.indexOf("Harumi");
if (i != -1)
cout << "Firstoccurrence of Harumi is at position " << i << endl;
The contains () function is used to find whether a vector container contains an object.
The count () function can be used to identify the number of times an object appears.
Insert (), replace (), remove (), prepend (), append () can be used to add, move, and delete an object. For larger vector containers, except append () and replace () functions, other functions are slow because when one location is moved in the memory, these functions will move objects in the vector container many times! If you want a container that can be quickly inserted or deleted in the middle, you can use QList or q1_list.
The resize () function can change the volume of the QVector vector container at any time. If the new vector container is larger than the previous one, QVector may need to re-allocate the entire vector container. QVector will pre-allocate two times the actual data size space, thus reducing the number of redistribution times.
Reserve () function. If you know in advance how many objects a vector container contains, you can call this function to pre-allocate a certain amount of memory space.
The capacity () function tells you the actual size and space occupied by the vector container.
Note: When constant operators and functions are used, QVector performs deep replication, which is caused by implicit sharing. The QVector value type must be assignable data type ). Most data types are of this type. But the compiler won't let you store a QWidget, but you can store the QWidget pointer! A few functions have additional requirements. For example, the types of indexOf () and lastIndexOf () expected values can support operator = (). These special requirements are recorded in the relevant function documentation.
Like other container classes, QVector supports Jave (QVectorIterator and QMutableVectorIterator) and STL-style iterators. In fact, these are rarely used. You can use index numbers!
You cannot insert, add, or replace a QVector. Otherwise, an error will be reported in your application!
QVector memory allocation policy
We all know that when the memory allocated by STL std: vector is filled up as a dynamic array, if we continue to add data, std :: vector will apply for another region that is twice the current size (if n> size, apply for n + space of the current capacity), and then copy the current content to the new memory, to achieve dynamic resizing:
size_type
_M_check_len(size_type __n, const char* __s) const
{
if (max_size() - size() < __n)
__throw_length_error(__N(__s));
const size_type __len = size() + std::max(size(), __n);
return (__len < size() || __len > max_size()) ? max_size() : __len;
}
The most intuitive way is to write a client program:
vector<int> ve(4, 8);
cout << "size : " << ve.size() << " capacity : " << ve.capacity() << endl;
for ( int i = 0; i < 14; ++i )
{
ve.push_back(9);
ve.push_back(0);
cout << "size : " << ve.size() << " capacity : " << ve.capacity() << endl;
}
The output is as follows: capacity doubles the capacity of the previous expansion:
Similarly, Qt has also implemented similar QVector in its QPS. To facilitate the service for Qt applications, it provides implicit sharing, write replication, and other mechanisms, java Style and C ++ Style interfaces are provided at the same time. The interfaces with the same function are changed to the same name:
Inline void push_back (const T & t) {append (t );}
So when the memory allocated by QVector is filled up, how does the memory expand? We can find out in the source code:
Let's take a look at QVector: append ():
const bool isTooSmall = uint(d->size + 1) > d->alloc;
if (!isDetached() || isTooSmall) {
QArrayData::AllocationOptions opt(isTooSmall ? QArrayData::Grow : QArrayData::Default);
reallocData(d->size, isTooSmall ? d->size + 1 : d->alloc, opt);
}
IsDetached () calls a reference count to determine whether the QVector is independent (not implicitly shared ). If the QVector is shared, when we want to call append () on the QVector that has been "copied, of course, you need to allocate a new memory and add elements to the memory. This is also called "Write-time replication ".
IsTooSmall tells us whether the current szie plus 1 has exceeded the current capacity (d-> alloc). If yes, you need to call reallocData to request memory. Because the memory allocation may be called by the replication policy during writing, reallocData () parameters vary depending on the isTooSmall parameter.
The QVector: reallocData () function calls QTypedArrayData: allocate (). The former executes the redirection of begin (), end (), and other pointers, and releases the original memory, the latter actually calls QArrayData: allocate (), and its function prototype is:
static QTypedArrayData *allocate(size_t capacity,
AllocationOptions options = Default) Q_REQUIRED_RESULT
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
return static_cast<QTypedArrayData *>(QArrayData::allocate(sizeof(T),
Q_ALIGNOF(AlignmentDummy), capacity, options));
}
The Q_ALIGNOF (AlignmentDummy) here is critical. AlignmentDummy is a class like the following:
Class AlignmentDummy {QArrayData header; T data ;};
QArrayData is the place where all consecutive Qt containers actually store data. It contains the following data members, that is, sizeof (QArrayData) is 16 bytes long:
QtPrivate::RefCount ref;
int size;
uint alloc : 31;
uint capacityReserved : 1;
qptrdiff offset; // in bytes from beginning of header
While Q_ALIGNOF is the alias of _ alignof _ in gcc, and _ alignof in MSVC is used to obtain the AlignmentDummy memory alignentdummy memory AlignmentDummy alignof, the above data member can know that the value of Q_ALIGNOF (QArrayData) is 4. When Q_ALIGNOF (AlignmentDummy) is greater than 4, it means that the memory occupied by the member variables of the QArrayData will have a gap (padding) between the memory alignof (AlignmentDummy) and the actual T-data ), therefore, we need to apply for additional padding space to ensure that all data can be properly placed.
After understanding this, let's take a look at QArrayData: allocate ()
QArrayData * QArrayData :: allocate (size_t objectSize, size_t alignment,
size_t capacity, AllocationOptions options)
{
// detect whether the order is a multiple of 2
Q_ASSERT (alignment> = Q_ALIGNOF (QArrayData)
&&! (alignment & (alignment-1)));
...
// Get the size when the QArrayData class is empty
size_t headerSize = sizeof (QArrayData);
// apply for additional alignment-Q_ALIGNOF (QArrayData) size padding bytes
// This will put the data in the right place
if (! (options & RawData))
headerSize + = (alignment-Q_ALIGNOF (QArrayData));
// If the array length exceeds the capacity, apply for new memory
if (options & Grow)
capacity = qAllocMore (int (objectSize * capacity), int (headerSize)) / int (objectSize);
// Total number of bytes that need to be applied
size_t allocSize = headerSize + objectSize * capacity;
QArrayData * header = static_cast <QArrayData *> (:: malloc (allocSize));
if (header) {
...
}
return header;
}
QAllocMore () is implemented in the qbyteArray. cpp file. This function returns an integer and the number of bytes required to return the data content:
int qAllocMore(int alloc, int extra)
{
Q_ASSERT(alloc >= 0 && extra >= 0);
Q_ASSERT_X(alloc < (1 << 30) - extra, "qAllocMore", "Requested size is too large!");
unsigned nalloc = alloc + extra;
// Round up to next power of 2
// Assuming container is growing, always overshoot
//--nalloc;
nalloc |= nalloc >> 1;
nalloc |= nalloc >> 2;
nalloc |= nalloc >> 4;
nalloc |= nalloc >> 8;
nalloc |= nalloc >> 16;
++nalloc;
Q_ASSERT(nalloc > unsigned(alloc + extra));
return nalloc - extra;
}
The function indicates that the request byte cannot exceed 2 ^ 30-extra. Note that extra is the sizeof (QArrayData) + sizeof (padding) we have obtained above ). Alloc is the size of the region where we store the actual data. nalloc is the total memory capacity we need.
If you are familiar with the following shift algorithms, you should know that the new value of the obtained nalloc is the nearest two factorial number greater than its original value. For example, if you enter 20, after the last + nalloc operation, nalloc is changed to 32.
When we opened the cloud, we finally arrived at the time of Qingtian. Back to our initial question: QVector continues to be inserted after full capacity. What is its memory growth strategy?
As we have seen above, we may have the answer: QVector capacity increases by 2 ^ n, that is, 2, 4, 8, 16, 32... OK. It's time to write the test code:
QVector<int> ve(2, 8);
qDebug() << "size : " << ve.size() << " capacity : " << ve.capacity();
for ( int i = 0; i < 20; ++i )
{
ve.append(9);
qDebug() << "size : " << ve.size() << " capacity : " << ve.capacity();
}
Enter the following information:
It seems strange that the capacity is not 2's n power? Do you still remember the sizeof (QArrayData) = 16 occupied by data members in the QArrayData class? These 16 bytes occupy the 4 capacity of our QVector <int>, that is, the actual QVector <int> capacity should be:
Now let's consider the situation with padding. When we create a QVector <quint64>, due to the memory alignment, there should be a gap between the QArrayData data members and the actual stored data, as a result, the available space exceeds 16 bytes:
As you can see, the actual space usage capacity is 3*8 = 24 bytes, of which 16 bytes is headerSize, and the remaining 8 bytes is the gap.
This should be clear (● 'authorization' ●)
So what is the main difference between this allocation policy and STL std: vector? Isn't it a doubling of every time?
Use int as the array data type and directly give an output result:
Similarly, if you add a data record to the full array of 100 capacities, QVector capacity expansion will apply for 128*4 (16-byte headerSize) bytes, while std :: vector will apply for 200*4 bytes.