Simplify QArrayDataOps::insert() for movable types

Avoid ever having to call a destructor and unify the code for
insertion at the front or at the end.

Change-Id: Ie50ae2d4a75477cfdae9d5bd4bddf268426d95b5
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Lars Knoll 2020-11-11 14:51:32 +01:00
parent faea8e2661
commit 6431565e0a
2 changed files with 79 additions and 303 deletions

View File

@ -76,50 +76,6 @@ struct QArrayExceptionSafetyPrimitives
using parameter_type = typename QArrayDataPointer<T>::parameter_type; using parameter_type = typename QArrayDataPointer<T>::parameter_type;
using iterator = typename QArrayDataPointer<T>::iterator; using iterator = typename QArrayDataPointer<T>::iterator;
// Constructs a range of elements at the specified position. If an exception
// is thrown during construction, already constructed elements are
// destroyed. By design, only one function (create/copy/clone/move) and only
// once is supposed to be called per class instance.
struct Constructor
{
T *const where;
size_t n = 0;
Constructor(T *w) noexcept : where(w) {}
qsizetype create(size_t size) noexcept(std::is_nothrow_default_constructible_v<T>)
{
n = 0;
while (n != size) {
new (where + n) T;
++n;
}
return qsizetype(std::exchange(n, 0));
}
qsizetype copy(const T *first, const T *last) noexcept(std::is_nothrow_copy_constructible_v<T>)
{
n = 0;
for (; first != last; ++first) {
new (where + n) T(*first);
++n;
}
return qsizetype(std::exchange(n, 0));
}
qsizetype clone(size_t size, parameter_type t) noexcept(std::is_nothrow_constructible_v<T, parameter_type>)
{
n = 0;
while (n != size) {
new (where + n) T(t);
++n;
}
return qsizetype(std::exchange(n, 0));
}
~Constructor() noexcept(std::is_nothrow_destructible_v<T>)
{
while (n)
where[--n].~T();
}
};
// Moves the data range in memory by the specified amount. Unless commit() // Moves the data range in memory by the specified amount. Unless commit()
// is called, the data is moved back to the original place at the end of // is called, the data is moved back to the original place at the end of
// object lifetime. // object lifetime.
@ -141,9 +97,10 @@ struct QArrayExceptionSafetyPrimitives
void commit() noexcept { displace = 0; } void commit() noexcept { displace = 0; }
~Displacer() noexcept ~Displacer() noexcept
{ {
if (displace) if constexpr (!std::is_nothrow_copy_constructible_v<T>)
::memmove(static_cast<void *>(begin), static_cast<void *>(begin + displace), if (displace)
(end - begin) * sizeof(T)); ::memmove(static_cast<void *>(begin), static_cast<void *>(begin + displace),
(end - begin) * sizeof(T));
} }
}; };
}; };
@ -829,6 +786,79 @@ public:
// using QGenericArrayOps<T>::destroyAll; // using QGenericArrayOps<T>::destroyAll;
typedef typename QGenericArrayOps<T>::parameter_type parameter_type; typedef typename QGenericArrayOps<T>::parameter_type parameter_type;
struct Inserter
{
QArrayDataPointer<T> *data;
T *displaceFrom;
T *displaceTo;
qsizetype nInserts = 0;
qsizetype bytes;
qsizetype increment = 1;
Inserter(QArrayDataPointer<T> *d, QArrayData::GrowthPosition pos)
: data(d), increment(pos == QArrayData::GrowsAtBeginning ? -1 : 1)
{
}
~Inserter() {
if constexpr (!std::is_nothrow_copy_constructible_v<T>) {
if (displaceFrom != displaceTo) {
::memmove(static_cast<void *>(displaceFrom), static_cast<void *>(displaceTo), bytes);
nInserts -= qAbs(displaceFrom - displaceTo);
}
}
if (increment < 0)
data->ptr -= nInserts;
data->size += nInserts;
}
T *displace(qsizetype pos, qsizetype n)
{
nInserts = n;
T *insertionPoint = data->ptr + pos;
if (increment > 0) {
displaceFrom = data->ptr + pos;
displaceTo = displaceFrom + n;
bytes = data->size - pos;
} else {
displaceFrom = data->ptr;
displaceTo = displaceFrom - n;
--insertionPoint;
bytes = pos;
}
bytes *= sizeof(T);
::memmove(static_cast<void *>(displaceTo), static_cast<void *>(displaceFrom), bytes);
return insertionPoint;
}
void insert(qsizetype pos, const T *source, qsizetype n)
{
T *where = displace(pos, n);
if (increment < 0)
source += n - 1;
while (n--) {
new (where) T(*source);
where += increment;
source += increment;
displaceFrom += increment;
}
}
void insert(qsizetype pos, const T &t, qsizetype n)
{
T *where = displace(pos, n);
while (n--) {
new (where) T(t);
where += increment;
displaceFrom += increment;
}
}
};
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;
@ -839,56 +869,7 @@ public:
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));
T *where = this->begin() + i; Inserter(this, pos).insert(i, data, n);
if (pos == QArrayData::GrowsAtBeginning)
insert(GrowsBackwardsTag{}, where, data, data + n);
else
insert(GrowsForwardTag{}, where, data, data + n);
}
void insert(GrowsForwardTag, T *where, const T *b, const T *e)
{
Q_ASSERT(this->isMutable() || (b == e && where == this->end()));
Q_ASSERT(!this->isShared() || (b == e && where == this->end()));
Q_ASSERT(where >= this->begin() && where <= this->end());
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end() || where == this->end()); // No overlap or append
Q_ASSERT((e - b) <= this->freeSpaceAtEnd());
typedef typename QArrayExceptionSafetyPrimitives<T>::Displacer ReversibleDisplace;
typedef typename QArrayExceptionSafetyPrimitives<T>::Constructor CopyConstructor;
// Provides strong exception safety guarantee,
// provided T::~T() nothrow
ReversibleDisplace displace(where, this->end(), e - b);
CopyConstructor copier(where);
const auto copiedSize = copier.copy(b, e);
displace.commit();
this->size += copiedSize;
}
void insert(GrowsBackwardsTag, T *where, const T *b, const T *e)
{
Q_ASSERT(this->isMutable() || (b == e && where == this->end()));
Q_ASSERT(!this->isShared() || (b == e && where == this->end()));
Q_ASSERT(where >= this->begin() && where <= this->end());
Q_ASSERT(b < e);
Q_ASSERT(e <= where || b > this->end() || where == this->end()); // No overlap or append
Q_ASSERT((e - b) <= this->freeSpaceAtBegin());
typedef typename QArrayExceptionSafetyPrimitives<T>::Constructor CopyConstructor;
typedef typename QArrayExceptionSafetyPrimitives<T>::Displacer ReversibleDisplace;
// Provides strong exception safety guarantee,
// provided T::~T() nothrow
ReversibleDisplace displace(this->begin(), where, -(e - b));
CopyConstructor copier(where - (e - b));
const auto copiedSize = copier.copy(b, e);
displace.commit();
this->ptr -= copiedSize;
this->size += copiedSize;
} }
void insert(qsizetype i, qsizetype n, parameter_type t) void insert(qsizetype i, qsizetype n, parameter_type t)
@ -902,57 +883,9 @@ public:
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));
T *where = this->begin() + i; Inserter(this, pos).insert(i, copy, n);
if (pos == QArrayData::GrowsAtBeginning)
insert(GrowsBackwardsTag{}, where, n, copy);
else
insert(GrowsForwardTag{}, where, n, copy);
} }
void insert(GrowsForwardTag, T *where, size_t n, parameter_type t)
{
Q_ASSERT(!this->isShared());
Q_ASSERT(n);
Q_ASSERT(where >= this->begin() && where <= this->end());
Q_ASSERT(size_t(this->freeSpaceAtEnd()) >= n);
typedef typename QArrayExceptionSafetyPrimitives<T>::Displacer ReversibleDisplace;
typedef typename QArrayExceptionSafetyPrimitives<T>::Constructor CopyConstructor;
// Provides strong exception safety guarantee,
// provided T::~T() nothrow
ReversibleDisplace displace(where, this->end(), qsizetype(n));
CopyConstructor copier(where);
const auto copiedSize = copier.clone(n, t);
displace.commit();
this->size += copiedSize;
}
void insert(GrowsBackwardsTag, T *where, size_t n, parameter_type t)
{
Q_ASSERT(!this->isShared());
Q_ASSERT(n);
Q_ASSERT(where >= this->begin() && where <= this->end());
Q_ASSERT(size_t(this->freeSpaceAtBegin()) >= n);
typedef typename QArrayExceptionSafetyPrimitives<T>::Constructor CopyConstructor;
typedef typename QArrayExceptionSafetyPrimitives<T>::Displacer ReversibleDisplace;
// Provides strong exception safety guarantee,
// provided T::~T() nothrow
ReversibleDisplace displace(this->begin(), where, -qsizetype(n));
CopyConstructor copier(where - n);
const auto copiedSize = copier.clone(n, t);
displace.commit();
this->ptr -= copiedSize;
this->size += copiedSize;
}
// use moving insert
using QGenericArrayOps<T>::insert;
template<typename... Args> template<typename... Args>
void emplace(GrowsForwardTag, T *where, Args &&... args) void emplace(GrowsForwardTag, T *where, Args &&... args)
{ {

View File

@ -85,7 +85,6 @@ private slots:
void selfEmplaceBackwards(); void selfEmplaceBackwards();
void selfEmplaceForward(); void selfEmplaceForward();
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
void exceptionSafetyPrimitives_constructor();
void exceptionSafetyPrimitives_displacer(); void exceptionSafetyPrimitives_displacer();
#endif #endif
}; };
@ -2349,162 +2348,6 @@ static QArrayDataPointer<T> createDataPointer(qsizetype capacity, qsizetype init
return adp; return adp;
} }
void tst_QArrayData::exceptionSafetyPrimitives_constructor()
{
using Prims = QtPrivate::QArrayExceptionSafetyPrimitives<ThrowingType>;
using Constructor = typename Prims::Constructor;
struct WatcherScope
{
WatcherScope() { throwingTypeWatcher().watch = true; }
~WatcherScope()
{
throwingTypeWatcher().watch = false;
throwingTypeWatcher().destroyedIds.clear();
}
};
const auto doConstruction = [] (auto &dataPointer, auto where, auto op) {
Constructor ctor(where);
dataPointer.size += op(ctor);
};
// empty ranges
{
auto data = createDataPointer<ThrowingType>(20, 10);
const auto originalSize = data.size;
const std::array<ThrowingType, 0> emptyRange{};
doConstruction(data, data.end(), [] (Constructor &ctor) { return ctor.create(0); });
QCOMPARE(data.size, originalSize);
doConstruction(data, data.end(), [&emptyRange] (Constructor &ctor) {
return ctor.copy(emptyRange.begin(), emptyRange.end());
});
QCOMPARE(data.size, originalSize);
doConstruction(data, data.end(), [] (Constructor &ctor) {
return ctor.clone(0, ThrowingType(42));
});
QCOMPARE(data.size, originalSize);
}
// successful create
{
auto data = createDataPointer<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
reference->appendInitialize(reference.size + 1);
doConstruction(data, data.end(), [] (Constructor &ctor) { return ctor.create(1); });
QCOMPARE(data.size, reference.size);
for (qsizetype i = 0; i < data.size; ++i)
QCOMPARE(data.data()[i], reference.data()[i]);
}
// successful copy
{
auto data = createDataPointer<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
const std::array<ThrowingType, 3> source = {
ThrowingType(42), ThrowingType(43), ThrowingType(44)
};
reference->copyAppend(source.begin(), source.end());
doConstruction(data, data.end(), [&source] (Constructor &ctor) {
return ctor.copy(source.begin(), source.end());
});
QCOMPARE(data.size, reference.size);
for (qsizetype i = 0; i < data.size; ++i)
QCOMPARE(data.data()[i], reference.data()[i]);
reference->copyAppend(2, source[0]);
doConstruction(data, data.end(), [&source] (Constructor &ctor) {
return ctor.clone(2, source[0]);
});
QCOMPARE(data.size, reference.size);
for (qsizetype i = 0; i < data.size; ++i)
QCOMPARE(data.data()[i], reference.data()[i]);
}
// failed create
{
auto data = createDataPointer<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
for (uint throwOnNthConstruction : {1, 3}) {
WatcherScope scope; Q_UNUSED(scope);
try {
ThrowingType::throwOnce = throwOnNthConstruction;
doConstruction(data, data.end(), [] (Constructor &ctor) {
return ctor.create(5);
});
} catch (const std::runtime_error &e) {
QCOMPARE(std::string(e.what()), ThrowingType::throwString);
QCOMPARE(data.size, reference.size);
for (qsizetype i = 0; i < data.size; ++i)
QCOMPARE(data.data()[i], reference.data()[i]);
QCOMPARE(throwingTypeWatcher().destroyedIds.size(), (throwOnNthConstruction - 1));
for (auto id : throwingTypeWatcher().destroyedIds)
QCOMPARE(id, 0);
}
}
}
// failed copy
{
auto data = createDataPointer<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
const std::array<ThrowingType, 4> source = {
ThrowingType(42), ThrowingType(43), ThrowingType(44), ThrowingType(170)
};
// copy range
for (uint throwOnNthConstruction : {1, 3}) {
WatcherScope scope; Q_UNUSED(scope);
try {
ThrowingType::throwOnce = throwOnNthConstruction;
doConstruction(data, data.end(), [&source] (Constructor &ctor) {
return ctor.copy(source.begin(), source.end());
});
} catch (const std::runtime_error &e) {
QCOMPARE(std::string(e.what()), ThrowingType::throwString);
QCOMPARE(data.size, reference.size);
for (qsizetype i = 0; i < data.size; ++i)
QCOMPARE(data.data()[i], reference.data()[i]);
const auto destroyedSize = throwingTypeWatcher().destroyedIds.size();
QCOMPARE(destroyedSize, (throwOnNthConstruction - 1));
for (size_t i = 0; i < destroyedSize; ++i)
QCOMPARE(throwingTypeWatcher().destroyedIds[i], source[destroyedSize - i - 1]);
}
}
// copy value
for (uint throwOnNthConstruction : {1, 3}) {
const ThrowingType value(512);
QVERIFY(QArrayDataPointer<ThrowingType>::pass_parameter_by_value == false);
WatcherScope scope; Q_UNUSED(scope);
try {
ThrowingType::throwOnce = throwOnNthConstruction;
doConstruction(data, data.end(), [&value] (Constructor &ctor) {
return ctor.clone(5, value);
});
} catch (const std::runtime_error &e) {
QCOMPARE(std::string(e.what()), ThrowingType::throwString);
QCOMPARE(data.size, reference.size);
for (qsizetype i = 0; i < data.size; ++i)
QCOMPARE(data.data()[i], reference.data()[i]);
QCOMPARE(throwingTypeWatcher().destroyedIds.size(), (throwOnNthConstruction - 1));
for (auto id : throwingTypeWatcher().destroyedIds)
QCOMPARE(id, 512);
}
}
}
}
void tst_QArrayData::exceptionSafetyPrimitives_displacer() void tst_QArrayData::exceptionSafetyPrimitives_displacer()
{ {
QVERIFY(QTypeInfo<ThrowingType>::isRelocatable); QVERIFY(QTypeInfo<ThrowingType>::isRelocatable);