Change QList's insert() and emplace() to always invalidate [pos, end())

Drop the "move left if pos <= size / 2" path in favor of reference
stability of insert and emplace operations

Leave the insert(0, ...) and emplace(0, ...) as special cases for
prepend optimization as invalidating [begin, end()) practically means
that we can reallocate behind the scenes

Doing this also simplifies the code a bit

Task-number: QTBUG-93019
Change-Id: I7c248f96d687e94a6a38f81ade901619ff2b4733
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
(cherry picked from commit 5e76c2acff2c70f2893306b16aeba230f3d6114a)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Andrei Golubev 2021-04-25 17:52:14 +02:00 committed by Qt Cherry-pick Bot
parent 36e0a4a98f
commit 24c75a28a6

View File

@ -150,8 +150,7 @@ public:
if (where < this->size) if (where < this->size)
::memmove(static_cast<void *>(insertionPoint + n), static_cast<void *>(insertionPoint), (this->size - where) * sizeof(T)); ::memmove(static_cast<void *>(insertionPoint + n), static_cast<void *>(insertionPoint), (this->size - where) * sizeof(T));
} else { } else {
if (where > 0) Q_ASSERT(where == 0);
::memmove(static_cast<void *>(this->ptr - n), static_cast<const void *>(this->ptr), where * sizeof(T));
this->ptr -= n; this->ptr -= n;
insertionPoint -= n; insertionPoint -= n;
} }
@ -162,7 +161,7 @@ public:
void insert(qsizetype i, const T *data, qsizetype n) void insert(qsizetype i, const T *data, qsizetype n)
{ {
typename Data::GrowthPosition pos = Data::GrowsAtEnd; typename Data::GrowthPosition pos = Data::GrowsAtEnd;
if (this->size != 0 && i <= (this->size >> 1)) if (this->size != 0 && i == 0)
pos = Data::GrowsAtBeginning; pos = Data::GrowsAtBeginning;
DataPointer oldData; DataPointer oldData;
@ -179,7 +178,7 @@ public:
T copy(t); T copy(t);
typename Data::GrowthPosition pos = Data::GrowsAtEnd; typename Data::GrowthPosition pos = Data::GrowsAtEnd;
if (this->size != 0 && i <= (this->size >> 1)) if (this->size != 0 && i == 0)
pos = Data::GrowsAtBeginning; pos = Data::GrowsAtBeginning;
this->detachAndGrow(pos, n, nullptr, nullptr); this->detachAndGrow(pos, n, nullptr, nullptr);
@ -210,7 +209,7 @@ public:
} }
T tmp(std::forward<Args>(args)...); T tmp(std::forward<Args>(args)...);
typename QArrayData::GrowthPosition pos = QArrayData::GrowsAtEnd; typename QArrayData::GrowthPosition pos = QArrayData::GrowsAtEnd;
if (this->size != 0 && i <= (this->size >> 1)) if (this->size != 0 && i == 0)
pos = QArrayData::GrowsAtBeginning; pos = QArrayData::GrowsAtBeginning;
this->detachAndGrow(pos, 1, nullptr, nullptr); this->detachAndGrow(pos, 1, nullptr, nullptr);
@ -396,24 +395,18 @@ public:
struct Inserter struct Inserter
{ {
QArrayDataPointer<T> *data; QArrayDataPointer<T> *data;
qsizetype increment = 1;
T *begin; T *begin;
qsizetype size; qsizetype size;
qsizetype sourceCopyConstruct = 0, nSource = 0, move = 0, sourceCopyAssign = 0; qsizetype sourceCopyConstruct = 0, nSource = 0, move = 0, sourceCopyAssign = 0;
T *end = nullptr, *last = nullptr, *where = nullptr; T *end = nullptr, *last = nullptr, *where = nullptr;
Inserter(QArrayDataPointer<T> *d, QArrayData::GrowthPosition pos) Inserter(QArrayDataPointer<T> *d) : data(d)
: data(d), increment(pos == QArrayData::GrowsAtBeginning ? -1 : 1)
{ {
begin = d->ptr; begin = d->ptr;
size = d->size; size = d->size;
if (increment < 0)
begin += size - 1;
} }
~Inserter() { ~Inserter() {
if (increment < 0)
begin -= size - 1;
data->ptr = begin; data->ptr = begin;
data->size = size; data->size = size;
} }
@ -421,8 +414,6 @@ public:
void setup(qsizetype pos, qsizetype n) void setup(qsizetype pos, qsizetype n)
{ {
if (increment > 0) {
end = begin + size; end = begin + size;
last = end - 1; last = end - 1;
where = begin + pos; where = begin + pos;
@ -436,20 +427,6 @@ public:
move = 0; move = 0;
sourceCopyAssign -= sourceCopyConstruct; sourceCopyAssign -= sourceCopyConstruct;
} }
} else {
end = begin - size;
last = end + 1;
where = end + pos;
sourceCopyConstruct = 0;
nSource = -n;
move = pos - n; // larger 0
sourceCopyAssign = -n;
if (n > pos) {
sourceCopyConstruct = pos - n;
move = 0;
sourceCopyAssign -= sourceCopyConstruct;
}
}
} }
void insert(qsizetype pos, const T *source, qsizetype n) void insert(qsizetype pos, const T *source, qsizetype n)
@ -458,12 +435,10 @@ public:
Q_UNUSED(oldSize); Q_UNUSED(oldSize);
setup(pos, n); setup(pos, n);
if (increment < 0)
source += n - 1;
// first create new elements at the end, by copying from elements // first create new elements at the end, by copying from elements
// to be inserted (if they extend past the current end of the array) // to be inserted (if they extend past the current end of the array)
for (qsizetype i = 0; i != sourceCopyConstruct; i += increment) { for (qsizetype i = 0; i != sourceCopyConstruct; ++i) {
new (end + i) T(source[nSource - sourceCopyConstruct + i]); new (end + i) T(source[nSource - sourceCopyConstruct + i]);
++size; ++size;
} }
@ -471,7 +446,7 @@ public:
// now move construct new elements at the end from existing elements inside // now move construct new elements at the end from existing elements inside
// the array. // the array.
for (qsizetype i = sourceCopyConstruct; i != nSource; i += increment) { for (qsizetype i = sourceCopyConstruct; i != nSource; ++i) {
new (end + i) T(std::move(*(end + i - nSource))); new (end + i) T(std::move(*(end + i - nSource)));
++size; ++size;
} }
@ -479,24 +454,24 @@ public:
Q_ASSERT(size == oldSize + n); Q_ASSERT(size == oldSize + n);
// now move assign existing elements towards the end // now move assign existing elements towards the end
for (qsizetype i = 0; i != move; i -= increment) for (qsizetype i = 0; i != move; --i)
last[i] = std::move(last[i - nSource]); last[i] = std::move(last[i - nSource]);
// finally copy the remaining elements from source over // finally copy the remaining elements from source over
for (qsizetype i = 0; i != sourceCopyAssign; i += increment) for (qsizetype i = 0; i != sourceCopyAssign; ++i)
where[i] = source[i]; where[i] = source[i];
} }
void insert(qsizetype pos, const T &t, qsizetype n) void insert(qsizetype pos, const T &t, qsizetype n)
{ {
qsizetype oldSize = size; const qsizetype oldSize = size;
Q_UNUSED(oldSize); Q_UNUSED(oldSize);
setup(pos, n); setup(pos, n);
// first create new elements at the end, by copying from elements // first create new elements at the end, by copying from elements
// to be inserted (if they extend past the current end of the array) // to be inserted (if they extend past the current end of the array)
for (qsizetype i = 0; i != sourceCopyConstruct; i += increment) { for (qsizetype i = 0; i != sourceCopyConstruct; ++i) {
new (end + i) T(t); new (end + i) T(t);
++size; ++size;
} }
@ -504,7 +479,7 @@ public:
// now move construct new elements at the end from existing elements inside // now move construct new elements at the end from existing elements inside
// the array. // the array.
for (qsizetype i = sourceCopyConstruct; i != nSource; i += increment) { for (qsizetype i = sourceCopyConstruct; i != nSource; ++i) {
new (end + i) T(std::move(*(end + i - nSource))); new (end + i) T(std::move(*(end + i - nSource)));
++size; ++size;
} }
@ -512,11 +487,11 @@ public:
Q_ASSERT(size == oldSize + n); Q_ASSERT(size == oldSize + n);
// now move assign existing elements towards the end // now move assign existing elements towards the end
for (qsizetype i = 0; i != move; i -= increment) for (qsizetype i = 0; i != move; --i)
last[i] = std::move(last[i - nSource]); last[i] = std::move(last[i - nSource]);
// finally copy the remaining elements from source over // finally copy the remaining elements from source over
for (qsizetype i = 0; i != sourceCopyAssign; i += increment) for (qsizetype i = 0; i != sourceCopyAssign; ++i)
where[i] = t; where[i] = t;
} }
@ -525,18 +500,18 @@ public:
setup(pos, 1); setup(pos, 1);
if (sourceCopyConstruct) { if (sourceCopyConstruct) {
Q_ASSERT(sourceCopyConstruct == increment); Q_ASSERT(sourceCopyConstruct == 1);
new (end) T(std::move(t)); new (end) T(std::move(t));
++size; ++size;
} else { } else {
// create a new element at the end by move constructing one existing element // create a new element at the end by move constructing one existing element
// inside the array. // inside the array.
new (end) T(std::move(*(end - increment))); new (end) T(std::move(*(end - 1)));
++size; ++size;
// now move assign existing elements towards the end // now move assign existing elements towards the end
for (qsizetype i = 0; i != move; i -= increment) for (qsizetype i = 0; i != move; --i)
last[i] = std::move(last[i - increment]); last[i] = std::move(last[i - 1]);
// and move the new item into place // and move the new item into place
*where = std::move(t); *where = std::move(t);
@ -546,31 +521,50 @@ public:
void insert(qsizetype i, const T *data, qsizetype n) void insert(qsizetype i, const T *data, qsizetype n)
{ {
typename Data::GrowthPosition pos = Data::GrowsAtEnd; const bool growsAtBegin = this->size != 0 && i == 0;
if (this->size != 0 && i <= (this->size >> 1)) const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
pos = Data::GrowsAtBeginning;
DataPointer oldData; DataPointer oldData;
this->detachAndGrow(pos, n, &data, &oldData); this->detachAndGrow(pos, n, &data, &oldData);
Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) || Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n)); (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));
Inserter(this, pos).insert(i, data, n); if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(this->freeSpaceAtBegin() >= n);
while (n) {
--n;
new (this->begin() - 1) T(data[n]);
--this->ptr;
++this->size;
}
} else {
Inserter(this).insert(i, data, n);
}
} }
void insert(qsizetype i, qsizetype n, parameter_type t) void insert(qsizetype i, qsizetype n, parameter_type t)
{ {
T copy(t); T copy(t);
typename Data::GrowthPosition pos = Data::GrowsAtEnd; const bool growsAtBegin = this->size != 0 && i == 0;
if (this->size != 0 && i <= (this->size >> 1)) const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
pos = Data::GrowsAtBeginning;
this->detachAndGrow(pos, n, nullptr, nullptr); this->detachAndGrow(pos, n, nullptr, nullptr);
Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) || Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n)); (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));
Inserter(this, pos).insert(i, copy, n); if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(this->freeSpaceAtBegin() >= n);
while (n--) {
new (this->begin() - 1) T(copy);
--this->ptr;
++this->size;
}
} else {
Inserter(this).insert(i, copy, n);
}
} }
template<typename... Args> template<typename... Args>
@ -591,13 +585,19 @@ public:
} }
} }
T tmp(std::forward<Args>(args)...); T tmp(std::forward<Args>(args)...);
typename QArrayData::GrowthPosition pos = QArrayData::GrowsAtEnd; const bool growsAtBegin = this->size != 0 && i == 0;
if (this->size != 0 && i <= (this->size >> 1)) const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
pos = QArrayData::GrowsAtBeginning;
this->detachAndGrow(pos, 1, nullptr, nullptr); this->detachAndGrow(pos, 1, nullptr, nullptr);
Inserter(this, pos).insertOne(i, std::move(tmp)); if (growsAtBegin) {
Q_ASSERT(this->freeSpaceAtBegin());
new (this->begin() - 1) T(std::move(tmp));
--this->ptr;
++this->size;
} else {
Inserter(this).insertOne(i, std::move(tmp));
}
} }
void erase(T *b, qsizetype n) void erase(T *b, qsizetype n)
@ -696,12 +696,7 @@ public:
qsizetype nInserts = 0; qsizetype nInserts = 0;
qsizetype bytes; qsizetype bytes;
qsizetype increment = 1; Inserter(QArrayDataPointer<T> *d) : data(d) { }
Inserter(QArrayDataPointer<T> *d, QArrayData::GrowthPosition pos)
: data(d), increment(pos == QArrayData::GrowsAtBeginning ? -1 : 1)
{
}
~Inserter() { ~Inserter() {
if constexpr (!std::is_nothrow_copy_constructible_v<T>) { if constexpr (!std::is_nothrow_copy_constructible_v<T>) {
if (displaceFrom != displaceTo) { if (displaceFrom != displaceTo) {
@ -709,8 +704,6 @@ public:
nInserts -= qAbs(displaceFrom - displaceTo); nInserts -= qAbs(displaceFrom - displaceTo);
} }
} }
if (increment < 0)
data->ptr -= nInserts;
data->size += nInserts; data->size += nInserts;
} }
@ -718,16 +711,9 @@ public:
{ {
nInserts = n; nInserts = n;
T *insertionPoint = data->ptr + pos; T *insertionPoint = data->ptr + pos;
if (increment > 0) {
displaceFrom = data->ptr + pos; displaceFrom = data->ptr + pos;
displaceTo = displaceFrom + n; displaceTo = displaceFrom + n;
bytes = data->size - pos; bytes = data->size - pos;
} else {
displaceFrom = data->ptr;
displaceTo = displaceFrom - n;
--insertionPoint;
bytes = pos;
}
bytes *= sizeof(T); bytes *= sizeof(T);
::memmove(static_cast<void *>(displaceTo), static_cast<void *>(displaceFrom), bytes); ::memmove(static_cast<void *>(displaceTo), static_cast<void *>(displaceFrom), bytes);
return insertionPoint; return insertionPoint;
@ -737,14 +723,11 @@ public:
{ {
T *where = displace(pos, n); T *where = displace(pos, n);
if (increment < 0)
source += n - 1;
while (n--) { while (n--) {
new (where) T(*source); new (where) T(*source);
where += increment; ++where;
source += increment; ++source;
displaceFrom += increment; ++displaceFrom;
} }
} }
@ -754,8 +737,8 @@ public:
while (n--) { while (n--) {
new (where) T(t); new (where) T(t);
where += increment; ++where;
displaceFrom += increment; ++displaceFrom;
} }
} }
@ -763,7 +746,7 @@ public:
{ {
T *where = displace(pos, 1); T *where = displace(pos, 1);
new (where) T(std::move(t)); new (where) T(std::move(t));
displaceFrom += increment; ++displaceFrom;
Q_ASSERT(displaceFrom == displaceTo); Q_ASSERT(displaceFrom == displaceTo);
} }
@ -772,31 +755,50 @@ public:
void insert(qsizetype i, const T *data, qsizetype n) void insert(qsizetype i, const T *data, qsizetype n)
{ {
typename Data::GrowthPosition pos = Data::GrowsAtEnd; const bool growsAtBegin = this->size != 0 && i == 0;
if (this->size != 0 && i <= (this->size >> 1)) const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
pos = Data::GrowsAtBeginning;
DataPointer oldData; DataPointer oldData;
this->detachAndGrow(pos, n, &data, &oldData); this->detachAndGrow(pos, n, &data, &oldData);
Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) || Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n)); (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));
Inserter(this, pos).insert(i, data, n); if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(this->freeSpaceAtBegin() >= n);
while (n) {
--n;
new (this->begin() - 1) T(data[n]);
--this->ptr;
++this->size;
}
} else {
Inserter(this).insert(i, data, n);
}
} }
void insert(qsizetype i, qsizetype n, parameter_type t) void insert(qsizetype i, qsizetype n, parameter_type t)
{ {
T copy(t); T copy(t);
typename Data::GrowthPosition pos = Data::GrowsAtEnd; const bool growsAtBegin = this->size != 0 && i == 0;
if (this->size != 0 && i <= (this->size >> 1)) const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
pos = Data::GrowsAtBeginning;
this->detachAndGrow(pos, n, nullptr, nullptr); this->detachAndGrow(pos, n, nullptr, nullptr);
Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) || Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n)); (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));
Inserter(this, pos).insert(i, copy, n); if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(this->freeSpaceAtBegin() >= n);
while (n--) {
new (this->begin() - 1) T(copy);
--this->ptr;
++this->size;
}
} else {
Inserter(this).insert(i, copy, n);
}
} }
template<typename... Args> template<typename... Args>
@ -817,13 +819,18 @@ public:
} }
} }
T tmp(std::forward<Args>(args)...); T tmp(std::forward<Args>(args)...);
typename QArrayData::GrowthPosition pos = QArrayData::GrowsAtEnd; const bool growsAtBegin = this->size != 0 && i == 0;
if (this->size != 0 && i <= (this->size >> 1)) const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
pos = QArrayData::GrowsAtBeginning;
this->detachAndGrow(pos, 1, nullptr, nullptr); this->detachAndGrow(pos, 1, nullptr, nullptr);
if (growsAtBegin) {
Inserter(this, pos).insertOne(i, std::move(tmp)); Q_ASSERT(this->freeSpaceAtBegin());
new (this->begin() - 1) T(std::move(tmp));
--this->ptr;
++this->size;
} else {
Inserter(this).insertOne(i, std::move(tmp));
}
} }
void erase(T *b, qsizetype n) void erase(T *b, qsizetype n)