QArrayDataOps::insert

Inserting elements anywhere in the array requires moving the elements
that follow out of the way and writing in the new ones. Trivial for PODs
and almost as much for movable types.

For "complex" types, we start by extending the array with placement new
and copy constructing elements. Then, copy assignment resets the
elements that were previously part of the array.

QPodArrayOps uses non-throwing operations. QMovableArrayOps provides
full rollback in the face of exceptions (strong guarantee).
QGenericArrayOps enforces that no data is leaked (all destructors
called) and invariants are maintained on exceptions -- the basic
guarantee.

With 3 different implementations, 2 of which are non-trivial, this
operation is a good showcase for QArrayOpsSelector and the different
implementations. As such, it warrants its own commit.

Change-Id: I21d9b4cb8e810db82623bcd1d78f583ebf3b6cb7
Reviewed-by: Bradley T. Hughes <bradley.hughes@nokia.com>
bb10
João Abecasis 2011-11-22 12:16:24 +01:00 committed by Qt by Nokia
parent 4da0b5fc02
commit 9c04f721a6
3 changed files with 380 additions and 0 deletions

View File

@ -88,6 +88,19 @@ struct QPodArrayOps
// As this is to be called only from destructor, it doesn't need to be
// exception safe; size not updated.
}
void insert(T *where, const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end()); // No overlap
Q_ASSERT(size_t(e - b) <= this->alloc - uint(this->size));
::memmove(where + (e - b), where, (this->end() - where) * sizeof(T));
::memcpy(where, b, (e - b) * sizeof(T));
this->size += (e - b);
}
};
template <class T>
@ -133,6 +146,71 @@ struct QGenericArrayOps
while (i != b)
(--i)->~T();
}
void insert(T *where, const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end()); // No overlap
Q_ASSERT(size_t(e - b) <= this->alloc - uint(this->size));
// Array may be truncated at where in case of exceptions
T *const end = this->end();
const T *readIter = end;
T *writeIter = end + (e - b);
const T *const step1End = where + qMax(e - b, end - where);
struct Destructor
{
Destructor(T *&iter)
: iter(&iter)
, end(iter)
{
}
void commit()
{
iter = &end;
}
~Destructor()
{
for (; *iter != end; --*iter)
(*iter)->~T();
}
T **iter;
T *end;
} destroyer(writeIter);
// Construct new elements in array
do {
--readIter, --writeIter;
new (writeIter) T(*readIter);
} while (writeIter != step1End);
while (writeIter != end) {
--e, --writeIter;
new (writeIter) T(*e);
}
destroyer.commit();
this->size += destroyer.end - end;
// Copy assign over existing elements
while (readIter != where) {
--readIter, --writeIter;
*writeIter = *readIter;
}
while (writeIter != where) {
--e, --writeIter;
*writeIter = *e;
}
}
};
template <class T>
@ -141,6 +219,70 @@ struct QMovableArrayOps
{
// using QGenericArrayOps<T>::copyAppend;
// using QGenericArrayOps<T>::destroyAll;
void insert(T *where, const T *b, const T *e)
{
Q_ASSERT(this->ref == 1);
Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end()); // No overlap
Q_ASSERT(size_t(e - b) <= this->alloc - uint(this->size));
// Provides strong exception safety guarantee,
// provided T::~T() nothrow
struct ReversibleDisplace
{
ReversibleDisplace(T *begin, T *end, size_t displace)
: begin(begin)
, end(end)
, displace(displace)
{
::memmove(begin + displace, begin, (end - begin) * sizeof(T));
}
void commit() { displace = 0; }
~ReversibleDisplace()
{
if (displace)
::memmove(begin, begin + displace, (end - begin) * sizeof(T));
}
T *const begin;
T *const end;
size_t displace;
} displace(where, this->end(), size_t(e - b));
struct CopyConstructor
{
CopyConstructor(T *where) : where(where) {}
void copy(const T *src, const T *const srcEnd)
{
n = 0;
for (; src != srcEnd; ++src) {
new (where + n) T(*src);
++n;
}
n = 0;
}
~CopyConstructor()
{
while (n)
where[--n].~T();
}
T *const where;
size_t n;
} copier(where);
copier.copy(b, e);
displace.commit();
this->size += (e - b);
}
};
template <class T, class = void>

View File

@ -135,6 +135,113 @@ public:
return *(end() - 1);
}
void reserve(size_t n)
{
if (n > capacity()
|| (n
&& !d->capacityReserved
&& (d->ref != 1 || (d->capacityReserved = 1, false)))) {
SimpleVector detached(Data::allocate(n, true));
static_cast<DataOps *>(detached.d)->copyAppend(constBegin(), constEnd());
detached.swap(*this);
}
}
void prepend(const_iterator first, const_iterator last)
{
if (!d->size) {
append(first, last);
return;
}
if (first == last)
return;
T *const begin = d->begin();
if (d->ref != 1
|| capacity() - size() < size_t(last - first)) {
SimpleVector detached(Data::allocate(
qMax(capacity(), size() + (last - first)), d->capacityReserved));
static_cast<DataOps *>(detached.d)->copyAppend(first, last);
static_cast<DataOps *>(detached.d)->copyAppend(begin, begin + d->size);
detached.swap(*this);
return;
}
static_cast<DataOps *>(d)->insert(begin, first, last);
}
void append(const_iterator first, const_iterator last)
{
if (first == last)
return;
if (d->ref != 1
|| capacity() - size() < size_t(last - first)) {
SimpleVector detached(Data::allocate(
qMax(capacity(), size() + (last - first)), d->capacityReserved));
if (d->size) {
const T *const begin = constBegin();
static_cast<DataOps *>(detached.d)->copyAppend(begin, begin + d->size);
}
static_cast<DataOps *>(detached.d)->copyAppend(first, last);
detached.swap(*this);
return;
}
static_cast<DataOps *>(d)->copyAppend(first, last);
}
void insert(int position, const_iterator first, const_iterator last)
{
if (position < 0)
position += d->size + 1;
if (position <= 0) {
prepend(first, last);
return;
}
if (size_t(position) >= size()) {
append(first, last);
return;
}
if (first == last)
return;
T *const begin = d->begin();
T *const where = begin + position;
const T *const end = begin + d->size;
if (d->ref != 1
|| capacity() - size() < size_t(last - first)) {
SimpleVector detached(Data::allocate(
qMax(capacity(), size() + (last - first)), d->capacityReserved));
if (position)
static_cast<DataOps *>(detached.d)->copyAppend(begin, where);
static_cast<DataOps *>(detached.d)->copyAppend(first, last);
static_cast<DataOps *>(detached.d)->copyAppend(where, end);
detached.swap(*this);
return;
}
// Temporarily copy overlapping data, if needed
if ((first >= where && first < end)
|| (last > where && last <= end)) {
SimpleVector tmp(first, last);
static_cast<DataOps *>(d)->insert(where, tmp.constBegin(), tmp.constEnd());
return;
}
static_cast<DataOps *>(d)->insert(where, first, last);
}
void swap(SimpleVector &other)
{
qSwap(d, other.d);

View File

@ -293,6 +293,67 @@ void tst_QArrayData::simpleVector()
QVERIFY(v5.isSharedWith(v6));
QVERIFY(!v1.isSharedWith(v5));
}
v1.prepend(array, array + sizeof(array)/sizeof(array[0]));
QCOMPARE(v1.size(), size_t(10));
QVERIFY(v1 == v8);
v6 = v1;
QVERIFY(v1.isSharedWith(v6));
v1.prepend(array, array + sizeof(array)/sizeof(array[0]));
QVERIFY(!v1.isSharedWith(v6));
QCOMPARE(v1.size(), size_t(20));
QCOMPARE(v6.size(), size_t(10));
for (int i = 0; i < 20; ++i)
QCOMPARE(v1[i], v6[i % 10]);
v1.clear();
v1.append(array, array + sizeof(array)/sizeof(array[0]));
QCOMPARE(v1.size(), size_t(10));
QVERIFY(v1 == v8);
v6 = v1;
QVERIFY(v1.isSharedWith(v6));
v1.append(array, array + sizeof(array)/sizeof(array[0]));
QVERIFY(!v1.isSharedWith(v6));
QCOMPARE(v1.size(), size_t(20));
QCOMPARE(v6.size(), size_t(10));
for (int i = 0; i < 20; ++i)
QCOMPARE(v1[i], v6[i % 10]);
v1.insert(0, v6.constBegin(), v6.constEnd());
QCOMPARE(v1.size(), size_t(30));
v6 = v1;
QVERIFY(v1.isSharedWith(v6));
v1.insert(10, v6.constBegin(), v6.constEnd());
QVERIFY(!v1.isSharedWith(v6));
QCOMPARE(v1.size(), size_t(60));
QCOMPARE(v6.size(), size_t(30));
for (int i = 0; i < 30; ++i)
QCOMPARE(v6[i], v8[i % 10]);
v1.insert(v1.size(), v6.constBegin(), v6.constEnd());
QCOMPARE(v1.size(), size_t(90));
v1.insert(-1, v8.constBegin(), v8.constEnd());
QCOMPARE(v1.size(), size_t(100));
v1.insert(-11, v8.constBegin(), v8.constEnd());
QCOMPARE(v1.size(), size_t(110));
v1.insert(-200, v8.constBegin(), v8.constEnd());
QCOMPARE(v1.size(), size_t(120));
for (int i = 0; i < 120; ++i)
QCOMPARE(v1[i], v8[i % 10]);
}
struct Deallocator
@ -669,12 +730,82 @@ void tst_QArrayData::arrayOps()
vs = SimpleVector<QString>(5, referenceString);
vo = SimpleVector<CountedObject>(5, referenceObject);
QCOMPARE(vi.size(), size_t(5));
QCOMPARE(vs.size(), size_t(5));
QCOMPARE(vo.size(), size_t(5));
QCOMPARE(CountedObject::liveCount, size_t(11));
for (int i = 0; i < 5; ++i) {
QCOMPARE(vi[i], referenceInt);
QVERIFY(vs[i].isSharedWith(referenceString));
QCOMPARE(vo[i].id, referenceObject.id);
}
////////////////////////////////////////////////////////////////////////////
// insert
vi.reserve(30);
vs.reserve(30);
vo.reserve(30);
QCOMPARE(vi.size(), size_t(5));
QCOMPARE(vs.size(), size_t(5));
QCOMPARE(vo.size(), size_t(5));
QVERIFY(vi.capacity() >= 30);
QVERIFY(vs.capacity() >= 30);
QVERIFY(vo.capacity() >= 30);
// Displace as many elements as array is extended by
vi.insert(0, intArray, intArray + 5);
vs.insert(0, stringArray, stringArray + 5);
vo.insert(0, objArray, objArray + 5);
QCOMPARE(vi.size(), size_t(10));
QCOMPARE(vs.size(), size_t(10));
QCOMPARE(vo.size(), size_t(10));
// Displace more elements than array is extended by
vi.insert(0, intArray, intArray + 5);
vs.insert(0, stringArray, stringArray + 5);
vo.insert(0, objArray, objArray + 5);
QCOMPARE(vi.size(), size_t(15));
QCOMPARE(vs.size(), size_t(15));
QCOMPARE(vo.size(), size_t(15));
// Displace less elements than array is extended by
vi.insert(5, vi.constBegin(), vi.constEnd());
vs.insert(5, vs.constBegin(), vs.constEnd());
vo.insert(5, vo.constBegin(), vo.constEnd());
QCOMPARE(vi.size(), size_t(30));
QCOMPARE(vs.size(), size_t(30));
QCOMPARE(vo.size(), size_t(30));
QCOMPARE(CountedObject::liveCount, size_t(36));
for (int i = 0; i < 15; ++i) {
QCOMPARE(vi[i], intArray[i % 5]);
QVERIFY(vs[i].isSharedWith(stringArray[i % 5]));
QCOMPARE(vo[i].id, objArray[i % 5].id);
}
for (int i = 15; i < 20; ++i) {
QCOMPARE(vi[i], referenceInt);
QVERIFY(vs[i].isSharedWith(referenceString));
QCOMPARE(vo[i].id, referenceObject.id);
}
for (int i = 20; i < 25; ++i) {
QCOMPARE(vi[i], intArray[i % 5]);
QVERIFY(vs[i].isSharedWith(stringArray[i % 5]));
QCOMPARE(vo[i].id, objArray[i % 5].id);
}
for (int i = 25; i < 30; ++i) {
QCOMPARE(vi[i], referenceInt);
QVERIFY(vs[i].isSharedWith(referenceString));
QCOMPARE(vo[i].id, referenceObject.id);
}
}
QTEST_APPLESS_MAIN(tst_QArrayData)