Commit 21c6ab58 authored by Nick Terrell's avatar Nick Terrell Committed by Facebook Github Bot

Fix small_vector insert for non-default constructible types

Summary:
Instead of default constructing in `moveObjectsRight`, insert the objects
directly into the hole. This has the added benefit of avoid a default
construction. The creation function has to be careful to return references
(rvalue, or lvalue) to avoid extra constructions without guaranteed NRVO.

I've constructed the elements in reverse order since it is strange to copy
backwards, then construct forwards, but don't have strong opinions, since
either way is correct.

Reviewed By: ot

Differential Revision: D14883034

fbshipit-source-id: bc0d19bf94705718500b80e2d6c202784134e7a5
parent 1f180ff8
...@@ -98,20 +98,27 @@ namespace detail { ...@@ -98,20 +98,27 @@ namespace detail {
/* /*
* Move objects in memory to the right into some uninitialized * Move objects in memory to the right into some uninitialized
* memory, where the region overlaps. This doesn't just use * memory, where the region overlaps. Then call create(size_t) for each
* std::move_backward because move_backward only works if all the * "hole" passing it the offset of the hole from first.
* memory is initialized to type T already. *
* This doesn't just use std::move_backward because move_backward only works
* if all the memory is initialized to type T already.
*
* The create function should return a reference type, to avoid
* extra copies and moves for non-trivial types.
*/ */
template <class T> template <class T, class Create>
typename std::enable_if< typename std::enable_if<!folly::is_trivially_copyable<T>::value>::type
std::is_default_constructible<T>::value && moveObjectsRightAndCreate(
!folly::is_trivially_copyable<T>::value>::type T* const first,
moveObjectsRight(T* first, T* lastConstructed, T* realLast) { T* const lastConstructed,
T* const realLast,
Create&& create) {
if (lastConstructed == realLast) { if (lastConstructed == realLast) {
return; return;
} }
T* end = first - 1; // Past the end going backwards. T* const end = first - 1; // Past the end going backwards.
T* out = realLast - 1; T* out = realLast - 1;
T* in = lastConstructed - 1; T* in = lastConstructed - 1;
{ {
...@@ -133,21 +140,33 @@ moveObjectsRight(T* first, T* lastConstructed, T* realLast) { ...@@ -133,21 +140,33 @@ moveObjectsRight(T* first, T* lastConstructed, T* realLast) {
*out = std::move(*in); *out = std::move(*in);
} }
for (; out >= lastConstructed; --out) { for (; out >= lastConstructed; --out) {
new (out) T(); new (out) T(create(out - first));
}
for (; out != end; --out) {
*out = create(out - first);
} }
rollback.dismiss(); rollback.dismiss();
} }
} }
// Specialization for trivially copyable types. The call to // Specialization for trivially copyable types. The call to
// std::move_backward here will just turn into a memmove. (TODO: // std::move_backward here will just turn into a memmove.
// change to std::is_trivially_copyable when that works.) // This must only be used with trivially copyable types because some of the
template <class T> // memory may be uninitialized, and std::move_backward() won't work when it
typename std::enable_if< // can't memmove().
!std::is_default_constructible<T>::value || template <class T, class Create>
folly::is_trivially_copyable<T>::value>::type typename std::enable_if<folly::is_trivially_copyable<T>::value>::type
moveObjectsRight(T* first, T* lastConstructed, T* realLast) { moveObjectsRightAndCreate(
T* const first,
T* const lastConstructed,
T* const realLast,
Create&& create) {
std::move_backward(first, lastConstructed, realLast); std::move_backward(first, lastConstructed, realLast);
T* const end = first - 1;
T* out = first + (realLast - lastConstructed) - 1;
for (; out != end; --out) {
*out = create(out - first);
}
} }
/* /*
...@@ -795,10 +814,16 @@ class small_vector : public detail::small_vector_base< ...@@ -795,10 +814,16 @@ class small_vector : public detail::small_vector_base<
offset); offset);
this->setSize(this->size() + 1); this->setSize(this->size() + 1);
} else { } else {
detail::moveObjectsRight( detail::moveObjectsRightAndCreate(
data() + offset, data() + size(), data() + size() + 1); data() + offset,
data() + size(),
data() + size() + 1,
[&](size_t i) -> value_type&& {
assert(i == 0);
(void)i;
return std::move(t);
});
this->setSize(size() + 1); this->setSize(size() + 1);
data()[offset] = std::move(t);
} }
return begin() + offset; return begin() + offset;
} }
...@@ -812,10 +837,16 @@ class small_vector : public detail::small_vector_base< ...@@ -812,10 +837,16 @@ class small_vector : public detail::small_vector_base<
iterator insert(const_iterator pos, size_type n, value_type const& val) { iterator insert(const_iterator pos, size_type n, value_type const& val) {
auto offset = pos - begin(); auto offset = pos - begin();
makeSize(size() + n); makeSize(size() + n);
detail::moveObjectsRight( detail::moveObjectsRightAndCreate(
data() + offset, data() + size(), data() + size() + n); data() + offset,
data() + size(),
data() + size() + n,
[&](size_t i) -> value_type const& {
assert(i < n);
(void)i;
return val;
});
this->setSize(size() + n); this->setSize(size() + n);
std::generate_n(begin() + offset, n, [&] { return val; });
return begin() + offset; return begin() + offset;
} }
...@@ -919,7 +950,8 @@ class small_vector : public detail::small_vector_base< ...@@ -919,7 +950,8 @@ class small_vector : public detail::small_vector_base<
// iterator insert functions from integral types (see insert().) // iterator insert functions from integral types (see insert().)
template <class It> template <class It>
iterator insertImpl(iterator pos, It first, It last, std::false_type) { iterator insertImpl(iterator pos, It first, It last, std::false_type) {
typedef typename std::iterator_traits<It>::iterator_category categ; using categ = typename std::iterator_traits<It>::iterator_category;
using it_ref = typename std::iterator_traits<It>::reference;
if (std::is_same<categ, std::input_iterator_tag>::value) { if (std::is_same<categ, std::input_iterator_tag>::value) {
auto offset = pos - begin(); auto offset = pos - begin();
while (first != last) { while (first != last) {
...@@ -929,13 +961,20 @@ class small_vector : public detail::small_vector_base< ...@@ -929,13 +961,20 @@ class small_vector : public detail::small_vector_base<
return begin() + offset; return begin() + offset;
} }
auto distance = std::distance(first, last); auto const distance = std::distance(first, last);
auto offset = pos - begin(); auto const offset = pos - begin();
assert(distance >= 0);
assert(offset >= 0);
makeSize(size() + distance); makeSize(size() + distance);
detail::moveObjectsRight( detail::moveObjectsRightAndCreate(
data() + offset, data() + size(), data() + size() + distance); data() + offset,
data() + size(),
data() + size() + distance,
[&](size_t i) -> it_ref {
assert(i < size_t(distance));
return *(first + i);
});
this->setSize(size() + distance); this->setSize(size() + distance);
std::copy_n(first, distance, begin() + offset);
return begin() + offset; return begin() + offset;
} }
......
...@@ -329,7 +329,7 @@ TEST(small_vector, leak_test) { ...@@ -329,7 +329,7 @@ TEST(small_vector, leak_test) {
} }
} }
TEST(small_vector, Insert) { TEST(small_vector, InsertTrivial) {
folly::small_vector<int> someVec(3, 3); folly::small_vector<int> someVec(3, 3);
someVec.insert(someVec.begin(), 12, 12); someVec.insert(someVec.begin(), 12, 12);
EXPECT_EQ(someVec.size(), 15); EXPECT_EQ(someVec.size(), 15);
...@@ -341,15 +341,38 @@ TEST(small_vector, Insert) { ...@@ -341,15 +341,38 @@ TEST(small_vector, Insert) {
} }
} }
// Make sure we insert a larger range so we can test placement new
// and move inserts
auto oldSize = someVec.size(); auto oldSize = someVec.size();
someVec.insert(someVec.begin() + 1, 12, 12); someVec.insert(someVec.begin() + 1, 30, 30);
EXPECT_EQ(someVec.size(), oldSize + 12); EXPECT_EQ(someVec.size(), oldSize + 30);
EXPECT_EQ(someVec[0], 12);
EXPECT_EQ(someVec[1], 30);
EXPECT_EQ(someVec[31], 12);
}
TEST(small_vector, InsertNontrivial) {
folly::small_vector<std::string> v1(6, "asd"), v2(7, "wat"); folly::small_vector<std::string> v1(6, "asd"), v2(7, "wat");
v1.insert(v1.begin() + 1, v2.begin(), v2.end()); v1.insert(v1.begin() + 1, v2.begin(), v2.end());
EXPECT_TRUE(v1.size() == 6 + 7); EXPECT_TRUE(v1.size() == 6 + 7);
EXPECT_EQ(v1.front(), "asd"); EXPECT_EQ(v1.front(), "asd");
EXPECT_EQ(v1[1], "wat"); EXPECT_EQ(v1[1], "wat");
// Insert without default constructor
class TestClass {
public:
// explicit TestClass() = default;
explicit TestClass(std::string s) : s(s) {}
std::string s;
};
folly::small_vector<TestClass> v3(5, TestClass("asd"));
folly::small_vector<TestClass> v4(10, TestClass("wat"));
v3.insert(v3.begin() + 1, v4.begin(), v4.end());
EXPECT_TRUE(v3.size() == 5 + 10);
EXPECT_EQ(v3[0].s, "asd");
EXPECT_EQ(v3[1].s, "wat");
EXPECT_EQ(v3[10].s, "wat");
EXPECT_EQ(v3[11].s, "asd");
} }
TEST(small_vector, Swap) { TEST(small_vector, Swap) {
...@@ -784,6 +807,23 @@ TEST(small_vector, SelfInsert) { ...@@ -784,6 +807,23 @@ TEST(small_vector, SelfInsert) {
EXPECT_EQ(vec[i - 1], "abc"); EXPECT_EQ(vec[i - 1], "abc");
EXPECT_EQ(vec[i], "abc"); EXPECT_EQ(vec[i], "abc");
} }
// range insert
for (int i = 2; i < 33; ++i) {
folly::small_vector<std::string> vec;
// reserve 2 * i space so we don't grow and invalidate references.
vec.reserve(2 * i);
for (int j = 0; j < i; ++j) {
vec.push_back("abc");
}
EXPECT_EQ(vec.size(), i);
vec.insert(vec.end() - 1, vec.begin(), vec.end() - 1);
EXPECT_EQ(vec.size(), 2 * i - 1);
for (auto const& val : vec) {
EXPECT_EQ(val, "abc");
}
}
} }
struct CheckedInt { struct CheckedInt {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment