Handle inserting from rdcarray into itself

* This self-insertion has the same kind of problem as overlapping ranges in
  memcpy, the act of inserting items can affect the input range by shifting
  things around. For inserting a single object we just copy it, for inserting a
  range we duplicate the whole array and then do the insert from the old range
  (and destruct it).
* Clearly this is not the most efficient implementation, a better solution would
  be to append onto the existing array (potentially not even reallocating then)
  and doing a rotate/shift in place.
This commit is contained in:
baldurk
2018-10-15 16:19:54 +01:00
parent 9a3c316fee
commit 69287da9ac
2 changed files with 119 additions and 1 deletions
+26 -1
View File
@@ -381,6 +381,20 @@ public:
void insert(size_t offs, const T *el, size_t count)
{
if(el + count >= begin() && end() >= el)
{
// we're inserting from ourselves, so if we did this blindly we'd potentially change the
// contents of the inserted range while doing the insertion.
// To fix that, we store our original data in a temp and copy into ourselves again. Then we
// insert from the range (which now points to the copy) and let it be destroyed.
// This could be more efficient as an append and then a rotate, but this is simpler for now.
rdcarray<T> copy;
copy.swap(*this);
this->reserve(copy.capacity());
*this = copy;
return insert(offs, el, count);
}
const size_t oldSize = size();
// invalid size
@@ -487,7 +501,18 @@ public:
insert(offs, in.begin(), in.size());
}
inline void insert(size_t offs, const rdcarray<T> &in) { insert(offs, in.data(), in.size()); }
inline void insert(size_t offs, const T &in) { insert(offs, &in, 1); }
inline void insert(size_t offs, const T &in)
{
if(&in < begin() || &in > end())
{
insert(offs, &in, 1);
}
else
{
T copy(in);
insert(offs, &copy, 1);
}
}
// helpful shortcut for 'append at end', basically a multi-element push_back
inline void append(const T *el, size_t count) { insert(size(), el, count); }
void erase(size_t offs, size_t count = 1)
+93
View File
@@ -473,6 +473,99 @@ TEST_CASE("Test array type", "[basictypes]")
CHECK(valueConstructor == 1);
CHECK(copyConstructor == 3);
};
SECTION("Inserting from array into itself")
{
constructor = 0;
valueConstructor = 0;
copyConstructor = 0;
destructor = 0;
rdcarray<ConstructorCounter> test;
// ensure no re-allocations due to size
test.reserve(100);
test.resize(5);
test[0].value = 10;
test[1].value = 20;
test[2].value = 30;
test[3].value = 40;
test[4].value = 50;
CHECK(constructor == 5);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == 0);
CHECK(destructor == 0);
CHECK(test.capacity() == 100);
CHECK(test.size() == 5);
ConstructorCounter tmp;
tmp.value = 999;
// 5 constructed objects in the array, and tmp
CHECK(constructor == 6);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == 0);
CHECK(destructor == 0);
// this should shift everything up, and copy-construct the element into place
test.insert(0, tmp);
CHECK(test.capacity() == 100);
CHECK(test.size() == 6);
// 5 copies and 5 destructs to shift the array contents up, then another copy for inserting tmp
CHECK(constructor == 6);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == 5 + 1);
CHECK(destructor == 5);
CHECK(test[0].value == 999);
CHECK(test[1].value == 10);
// this should copy the value, then do an insert
test.insert(0, test[0]);
CHECK(test.capacity() == 100);
CHECK(test.size() == 7);
// on top of the above, another 6 copies & destructs to shift the array contents, 1 copy for
// inserting test[0], and a copy&destruct of the temporary copy
CHECK(constructor == 6);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == (5 + 1) + 6 + 1 + 1);
CHECK(destructor == (5) + 6 + 1);
CHECK(test[0].value == 999);
CHECK(test[1].value == 999);
CHECK(test[2].value == 10);
// this should detect the overlapped range, and duplicate the whole object
test.insert(0, test.data(), 3);
// ensure the correct size and allocated space
CHECK(test.capacity() == 100);
CHECK(test.size() == 10);
CHECK(test[0].value == 999);
CHECK(test[1].value == 999);
CHECK(test[2].value == 10);
CHECK(test[3].value == 999);
CHECK(test[4].value == 999);
CHECK(test[5].value == 10);
// on top of the above:
// - 7 copies and destructs for the duplication (copies into the new storage, destructs from the
// old storage)
// - 7 copies and destructs for shifting the array contents
// - 3 copies for the inserted items
CHECK(constructor == 6);
CHECK(valueConstructor == 0);
CHECK(copyConstructor == (5 + 1 + 6 + 1 + 1) + 7 + 7 + 3);
CHECK(destructor == (5 + 6 + 1) + 7 + 7);
};
};
#define CHECK_NULL_TERM(str) CHECK(str.c_str()[str.size()] == '\0');