QVector: implement methods for adding new elements constructed in place

Fixes: QTBUG-80293
Change-Id: I687dc05a9ad2bad7bab3dc2b1173edf75550d57e
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Vitaly Fanaskov 2019-12-12 11:28:52 +01:00
parent 71436d5499
commit f19fbbdb2f
5 changed files with 328 additions and 51 deletions

View File

@ -115,6 +115,29 @@ vector.append(std::move(three));
//! [move-append]
//! [emplace]
QVector<QString> vector{"a", "ccc"};
vector.emplace(1, 2, 'b');
// vector: ["a", "bb", "ccc"]
//! [emplace]
//! [emplace-back]
QVector<QString> vector{"one", "two"};
vector.emplaceBack(3, 'a');
qDebug() << vector;
// vector: ["one", "two", "aaa"]
//! [emplace-back]
//! [emplace-back-ref]
QVector<QString> vector;
auto &ref = vector.emplaceBack();
ref = "one";
// vector: ["one"]
//! [emplace-back-ref]
//! [8]
QVector<QString> vector;
vector.prepend("one");

View File

@ -117,6 +117,9 @@ struct QPodArrayOps
this->size += int(n);
}
template <typename ...Args>
void emplaceBack(Args&&... args) { this->emplace(this->end(), T(std::forward<Args>(args)...)); }
void truncate(size_t newSize)
{
Q_ASSERT(this->isMutable());
@ -163,16 +166,28 @@ struct QPodArrayOps
*where++ = t;
}
void insert(T *where, T &&t)
template <typename ...Args>
void createInPlace(T *where, Args&&... args) { new (where) T(std::forward<Args>(args)...); }
template <typename ...Args>
void emplace(T *where, Args&&... args)
{
Q_ASSERT(!this->isShared());
Q_ASSERT(where >= this->begin() && where <= this->end());
Q_ASSERT(this->allocatedCapacity() - this->size >= 1);
::memmove(static_cast<void *>(where + 1), static_cast<void *>(where),
(static_cast<const T*>(this->end()) - where) * sizeof(T));
this->size += 1;
new (where) T(std::move(t));
if (where == this->end()) {
new (this->end()) T(std::forward<Args>(args)...);
} else {
// Preserve the value, because it might be a reference to some part of the moved chunk
T t(std::forward<Args>(args)...);
::memmove(static_cast<void *>(where + 1), static_cast<void *>(where),
(static_cast<const T*>(this->end()) - where) * sizeof(T));
*where = t;
}
++this->size;
}
@ -289,6 +304,12 @@ struct QGenericArrayOps
}
}
template <typename ...Args>
void emplaceBack(Args&&... args)
{
this->emplace(this->end(), std::forward<Args>(args)...);
}
void truncate(size_t newSize)
{
Q_ASSERT(this->isMutable());
@ -444,31 +465,20 @@ struct QGenericArrayOps
}
}
void insert(T *where, T &&t)
template <typename ...Args>
void createInPlace(T *where, Args&&... args) { new (where) T(std::forward<Args>(args)...); }
template <typename iterator, typename ...Args>
void emplace(iterator where, Args&&... args)
{
Q_ASSERT(!this->isShared());
Q_ASSERT(where >= this->begin() && where <= this->end());
Q_ASSERT(this->allocatedCapacity() - this->size >= 1);
// Array may be truncated at where in case of exceptions
T *const end = this->end();
if (where != end) {
// Move elements in array
T *readIter = end - 1;
T *writeIter = end;
new (writeIter) T(std::move(*readIter));
while (readIter > where) {
--readIter;
--writeIter;
*writeIter = std::move(*readIter);
}
*where = std::move(t);
} else {
new (where) T(std::move(t));
}
createInPlace(this->end(), std::forward<Args>(args)...);
++this->size;
std::rotate(where, this->end() - 1, this->end());
}
void erase(T *b, T *e)

View File

@ -242,10 +242,14 @@ public:
void append(const_reference t)
{ append(const_iterator(std::addressof(t)), const_iterator(std::addressof(t)) + 1); }
void append(const_iterator i1, const_iterator i2);
void append(value_type &&t);
void append(rvalue_ref t) { emplaceBack(std::move(t)); }
void append(const QVector<T> &l) { append(l.constBegin(), l.constEnd()); }
void prepend(rvalue_ref t);
void prepend(const T &t);
template <typename ...Args>
reference emplaceBack(Args&&... args) { return *emplace(count(), std::forward<Args>(args)...); }
iterator insert(int i, parameter_type t)
{ return insert(i, 1, t); }
iterator insert(int i, int n, parameter_type t);
@ -264,7 +268,17 @@ public:
Q_ASSERT_X(isValidIterator(before), "QVector::insert", "The specified iterator argument 'before' is invalid");
return insert(std::distance(constBegin(), before), std::move(t));
}
iterator insert(int i, rvalue_ref t);
iterator insert(int i, rvalue_ref t) { return emplace(i, std::move(t)); }
template <typename ...Args>
iterator emplace(const_iterator before, Args&&... args)
{
Q_ASSERT_X(isValidIterator(before), "QVector::emplace", "The specified iterator argument 'before' is invalid");
return emplace(std::distance(constBegin(), before), std::forward<Args>(args)...);
}
template <typename ...Args>
iterator emplace(int i, Args&&... args);
#if 0
template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last );
@ -388,6 +402,10 @@ public:
inline void push_front(const T &t) { prepend(t); }
void pop_back() { removeLast(); }
void pop_front() { removeFirst(); }
template <typename ...Args>
reference emplace_back(Args&&... args) { return emplaceBack(std::forward<Args>(args)...); }
inline bool empty() const
{ return d->size == 0; }
inline reference front() { return first(); }
@ -537,27 +555,6 @@ inline void QVector<T>::append(const_iterator i1, const_iterator i2)
}
}
template <typename T>
inline void QVector<T>::append(value_type &&t)
{
const size_t newSize = size() + 1;
const bool isTooSmall = newSize > d->allocatedCapacity();
const bool isOverlapping = std::addressof(*d->begin()) <= std::addressof(t)
&& std::addressof(t) < std::addressof(*d->end());
if (isTooSmall || d->needsDetach() || Q_UNLIKELY(isOverlapping)) {
typename Data::ArrayOptions flags = d->detachFlags();
if (isTooSmall)
flags |= Data::GrowsForward;
DataPointer detached(Data::allocate(d->detachCapacity(newSize), flags));
detached->copyAppend(constBegin(), constEnd());
detached->moveAppend(std::addressof(t), std::addressof(t) + 1);
d.swap(detached);
} else {
// we're detached and we can just move data around
d->moveAppend(std::addressof(t), std::addressof(t) + 1);
}
}
template <typename T>
inline typename QVector<T>::iterator
QVector<T>::insert(int i, int n, parameter_type t)
@ -592,10 +589,11 @@ QVector<T>::insert(int i, int n, parameter_type t)
}
template <typename T>
template <typename ...Args>
typename QVector<T>::iterator
QVector<T>::insert(int i, rvalue_ref t)
QVector<T>::emplace(int i, Args&&... args)
{
Q_ASSERT_X(size_t(i) <= size_t(d->size), "QVector<T>::insert", "index out of range");
Q_ASSERT_X(i >= 0 && i <= d->size, "QVector<T>::insert", "index out of range");
const size_t newSize = size() + 1;
if (d->needsDetach() || newSize > d->allocatedCapacity()) {
@ -605,12 +603,24 @@ QVector<T>::insert(int i, rvalue_ref t)
DataPointer detached(Data::allocate(d->detachCapacity(newSize), flags));
const_iterator where = constBegin() + i;
// First, create an element to handle cases, when a user moves
// the element from a container to the same container
detached->createInPlace(detached.begin() + i, std::forward<Args>(args)...);
// Then, put the first part of the elements to the new location
detached->copyAppend(constBegin(), where);
detached->moveAppend(std::addressof(t), std::addressof(t) + 1);
// After that, increase the actual size, because we created
// one extra element
++detached.size;
// Finally, put the rest of the elements to the new location
detached->copyAppend(where, constEnd());
d.swap(detached);
} else {
d->insert(d.begin() + i, std::move(t));
d->emplace(d.begin() + i, std::forward<Args>(args)...);
}
return d.begin() + i;
}

View File

@ -646,6 +646,28 @@
\sa append(), insert()
*/
/*!
\fn template <typename T> template <typename ...Args> T &QVector<T>::emplaceBack(Args&&... args)
\fn template <typename T> template <typename ...Args> T &QVector<T>::emplace_back(Args&&... args)
Adds a new element to the end for the container. This new element
is constructed in-place using \a args as the arguments for its
construction.
Returns a reference to the new element.
Example:
\snippet code/src_corelib_tools_qvector.cpp emplace-back
It is also possible to access a newly created object by using
returned reference:
\snippet code/src_corelib_tools_qvector.cpp emplace-back-ref
This is the same as vector.emplace(vector.size(), \a args).
\sa emplace
*/
/*! \fn template <typename T> void QVector<T>::insert(int i, const T &value)
\fn template <typename T> void QVector<T>::insert(int i, T &&value)
@ -693,6 +715,26 @@
first of the inserted items.
*/
/*!
\fn template <typename T> template <typename ...Args> QVector<T>::iterator QVector<T>::emplace(int i, Args&&... args)
Extends the container by inserting a new element at position \a i.
This new element is constructed in-place using \a args as the
arguments for its construction.
Returns an iterator to the new element.
Example:
\snippet code/src_corelib_tools_qvector.cpp emplace
\note It is garanteed that the element will be created in place
at the beginning, but after that it might be copied or
moved to the right position.
\sa emplaceBack
*/
/*! \fn template <typename T> void QVector<T>::replace(int i, const T &value)
Replaces the item at index position \a i with \a value.
@ -838,6 +880,17 @@
\sa takeFirst(), removeLast()
*/
/*!
\fn template <typename T> template <typename ...Args> QVector<T>::iterator QVector<T>::emplace(QVector<T>::iterator before, Args&&... args)
\overload
Creates a new element in front of the item pointed to by the
iterator \a before. This new element is constructed in-place
using \a args as the arguments for its construction.
Returns an iterator to the new element.
*/
/*! \fn template <typename T> QVector<T> &QVector<T>::fill(const T &value, int size = -1)

View File

@ -324,6 +324,18 @@ private slots:
void swapItemsAt() const;
void emplaceInt();
void emplaceCustom();
void emplaceMovable();
void emplaceConsistentWithStdVectorInt();
void emplaceConsistentWithStdVectorCustom();
void emplaceConsistentWithStdVectorMovable();
void emplaceReturnsIterator();
void emplaceBack();
void emplaceBackReturnsRef();
void emplaceWithElementFromTheSameContainer();
void emplaceWithElementFromTheSameContainer_data();
private:
template<typename T> void copyConstructor() const;
template<typename T> void add() const;
@ -349,6 +361,8 @@ private:
template<typename T> void initializeList();
template<typename T> void detach() const;
template<typename T> void detachThreadSafety() const;
template<typename T> void emplaceImpl() const;
template<typename T> void emplaceConsistentWithStdVectorImpl() const;
};
@ -2645,5 +2659,172 @@ void tst_QVector::swapItemsAt() const
QCOMPARE(copy.at(2), 2);
}
void tst_QVector::emplaceInt()
{
emplaceImpl<int>();
}
void tst_QVector::emplaceCustom()
{
emplaceImpl<Custom>();
}
void tst_QVector::emplaceMovable()
{
emplaceImpl<Movable>();
}
void tst_QVector::emplaceConsistentWithStdVectorInt()
{
emplaceConsistentWithStdVectorImpl<int>();
}
void tst_QVector::emplaceConsistentWithStdVectorCustom()
{
emplaceConsistentWithStdVectorImpl<Custom>();
}
void tst_QVector::emplaceConsistentWithStdVectorMovable()
{
emplaceConsistentWithStdVectorImpl<Movable>();
}
void tst_QVector::emplaceReturnsIterator()
{
QVector<Movable> vec;
vec.emplace(0, 'k')->i = 'p';
QCOMPARE(vec[0].i, 'p');
}
void tst_QVector::emplaceBack()
{
QScopedValueRollback<QAtomicInt> rollback(Movable::counter, 0);
QVector<Movable> vec;
vec.emplaceBack('k');
QCOMPARE(Movable::counter, 1);
}
void tst_QVector::emplaceBackReturnsRef()
{
QVector<Movable> vec;
vec.emplaceBack('k').i = 'p';
QCOMPARE(vec.at(0).i, 'p');
}
void tst_QVector::emplaceWithElementFromTheSameContainer()
{
QFETCH(int, elementPos);
QFETCH(int, insertPos);
QFETCH(bool, doCopy);
QVector<QString> vec {"a", "b", "c", "d", "e"};
const QString e = vec[elementPos];
if (doCopy)
vec.emplace(insertPos, vec[elementPos]);
else
vec.emplace(insertPos, std::move(vec[elementPos]));
QCOMPARE(vec[insertPos], e);
}
void tst_QVector::emplaceWithElementFromTheSameContainer_data()
{
QTest::addColumn<int>("elementPos");
QTest::addColumn<int>("insertPos");
QTest::addColumn<bool>("doCopy");
for (int i = 0; i < 2; ++i) {
const bool doCopy = i == 0;
const char *opName = doCopy ? "copy" : "move";
QTest::addRow("%s: begin -> end" , opName) << 0 << 5 << doCopy;
QTest::addRow("%s: begin -> middle", opName) << 0 << 2 << doCopy;
QTest::addRow("%s: middle -> begin" , opName) << 2 << 0 << doCopy;
QTest::addRow("%s: middle -> end" , opName) << 2 << 5 << doCopy;
QTest::addRow("%s: end -> middle", opName) << 4 << 2 << doCopy;
QTest::addRow("%s: end -> begin" , opName) << 4 << 0 << doCopy;
}
}
template<typename T>
void tst_QVector::emplaceImpl() const
{
QVector<T> vec {'a', 'b', 'c', 'd'};
vec.emplace(2, 'k');
QCOMPARE(vec[2], T('k'));
}
template <class T>
static void vecEq(const QVector<T> &qVec, const std::vector<T> &stdVec)
{
QCOMPARE(std::size_t(qVec.size()), stdVec.size());
QVERIFY(std::equal(qVec.begin(), qVec.end(), stdVec.begin(), stdVec.end()));
}
template <class T>
static void squeezeVec(QVector<T> &qVec, std::vector<T> &stdVec)
{
qVec.squeeze();
stdVec.shrink_to_fit();
}
template<typename T>
void tst_QVector::emplaceConsistentWithStdVectorImpl() const
{
QVector<T> qVec {'a', 'b', 'c', 'd', 'e'};
std::vector<T> stdVec {'a', 'b', 'c', 'd', 'e'};
vecEq(qVec, stdVec);
qVec.emplaceBack('f');
stdVec.emplace_back('f');
vecEq(qVec, stdVec);
qVec.emplace(3, 'g');
stdVec.emplace(stdVec.begin() + 3, 'g');
vecEq(qVec, stdVec);
qVec.emplaceBack(std::move(qVec[0]));
stdVec.emplace_back(std::move(stdVec[0]));
vecEq(qVec, stdVec);
squeezeVec(qVec, stdVec);
qVec.emplaceBack(std::move(qVec[1]));
stdVec.emplace_back(std::move(stdVec[1]));
vecEq(qVec, stdVec);
squeezeVec(qVec, stdVec);
qVec.emplace(3, std::move(qVec[5]));
stdVec.emplace(stdVec.begin() + 3, std::move(stdVec[5]));
vecEq(qVec, stdVec);
qVec.emplaceBack(qVec[3]);
stdVec.emplace_back(stdVec[3]);
vecEq(qVec, stdVec);
squeezeVec(qVec, stdVec);
qVec.emplaceBack(qVec[4]);
stdVec.emplace_back(stdVec[4]);
vecEq(qVec, stdVec);
squeezeVec(qVec, stdVec);
qVec.emplace(5, qVec[7]);
stdVec.emplace(stdVec.begin() + 5, stdVec[7]);
vecEq(qVec, stdVec);
}
QTEST_MAIN(tst_QVector)
#include "tst_qvector.moc"