From 68c4355807856f79ab5ed2f5d4f90c18e464478b Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Wed, 30 Nov 2022 13:19:02 -0500 Subject: [PATCH] CoordinateSequence: create using external buffer --- capi/geos_c.cpp | 6 + capi/geos_c.h.in | 27 +- capi/geos_ts_c.cpp | 8 + include/geos/geom/CoordinateSequence.h | 25 +- include/geos/util/Vector.h | 236 +++++++++++++ src/geom/CoordinateSequence.cpp | 33 +- tests/unit/CMakeLists.txt | 4 +- tests/unit/capi/GEOSCoordSeqTest.cpp | 23 ++ tests/unit/geom/CoordinateSequenceTest.cpp | 54 ++- tests/unit/util/VectorTest.cpp | 372 +++++++++++++++++++++ 10 files changed, 773 insertions(+), 15 deletions(-) create mode 100644 include/geos/util/Vector.h create mode 100644 tests/unit/util/VectorTest.cpp diff --git a/capi/geos_c.cpp b/capi/geos_c.cpp index d8cbdd9f43..3bbb1cb2da 100644 --- a/capi/geos_c.cpp +++ b/capi/geos_c.cpp @@ -983,6 +983,12 @@ extern "C" { return GEOSCoordSeq_create_r(handle, size, dims); } + CoordinateSequence* + GEOSCoordSeq_createFromBuffer(double* buf, unsigned int size, int hasZ, int hasM) + { + return GEOSCoordSeq_createFromBuffer_r(handle, buf, size, hasZ, hasM); + } + CoordinateSequence* GEOSCoordSeq_copyFromBuffer(const double* buf, unsigned int size, int hasZ, int hasM) { diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in index 6a9d0fd1fa..422825b92a 100644 --- a/capi/geos_c.h.in +++ b/capi/geos_c.h.in @@ -396,6 +396,14 @@ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_create_r( unsigned int size, unsigned int dims); +/** \see GEOSCoordSeq_createFromBuffer */ +extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_createFromBuffer_r( + GEOSContextHandle_t handle, + double* buf, + unsigned int size, + int hasZ, + int hasM); + /** \see GEOSCoordSeq_copyFromBuffer */ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_copyFromBuffer_r( GEOSContextHandle_t handle, @@ -2023,11 +2031,24 @@ extern void GEOS_DLL GEOSFree(void *buffer); extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_create(unsigned int size, unsigned int dims); /** -* Create a coordinate sequence by copying from a buffer of doubles (XYXY or XYZXYZ) +* Create a coordinate sequence using from a buffer of interleaved doubles (XYXY, XYZXYZ, etc.) +* If possible, the CoordinateSequence will use the values directly from the buffer without +* copying. (Currently, this is supported for XYZ and XYZM buffers only.) +* +* \param buf pointer to buffer +* \param size number of coordinates in the sequence +* \param hasZ does buffer have Z values? +* \param hasM does buffer have M values? +* \return the sequence or NULL on exception +*/ +extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_createFromBuffer(double* buf, unsigned int size, int hasZ, int hasM); + +/** +* Create a coordinate sequence by copying from a buffer of doubles (XYXY, XYZXYZ, etc.) * \param buf pointer to buffer * \param size number of coordinates in the sequence * \param hasZ does buffer have Z values? -* \param hasM does buffer have M values? (they will be ignored) +* \param hasM does buffer have M values? * \return the sequence or NULL on exception */ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_copyFromBuffer(const double* buf, unsigned int size, int hasZ, int hasM); @@ -2044,7 +2065,7 @@ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_copyFromBuffer(const double* buf extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_copyFromArrays(const double* x, const double* y, const double* z, const double* m, unsigned int size); /** -* Copy the contents of a coordinate sequence to a buffer of doubles (XYXY or XYZXYZ) +* Copy the contents of a coordinate sequence to a buffer of doubles (XYXY, XYZXYZ, etc.) * \param s sequence to copy * \param buf buffer to which coordinates should be copied * \param hasZ copy Z values to buffer? diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp index 6e49e4449f..b45c5f5068 100644 --- a/capi/geos_ts_c.cpp +++ b/capi/geos_ts_c.cpp @@ -2430,6 +2430,14 @@ extern "C" { }); } + CoordinateSequence* + GEOSCoordSeq_createFromBuffer_r(GEOSContextHandle_t extHandle, double* buf, unsigned int size, int hasZ, int hasM) + { + return execute(extHandle, [&]() { + return new CoordinateSequence(buf, size, hasZ, hasM); + }); + } + CoordinateSequence* GEOSCoordSeq_copyFromBuffer_r(GEOSContextHandle_t extHandle, const double* buf, unsigned int size, int hasZ, int hasM) { diff --git a/include/geos/geom/CoordinateSequence.h b/include/geos/geom/CoordinateSequence.h index 49518aff23..c482980a74 100644 --- a/include/geos/geom/CoordinateSequence.h +++ b/include/geos/geom/CoordinateSequence.h @@ -19,6 +19,7 @@ #include // for applyCoordinateFilter #include +#include #include #include @@ -89,12 +90,28 @@ class GEOS_DLL CoordinateSequence { * are not actually stored in the sequence. * * @param size size of the sequence to create - * @param hasz true if the stored - * @param hasm - * @param initialize + * @param hasz true if the sequence should store z values + * @param hasm true if the sequence should store m values + * @param initialize true if the sequence should be initialized to default coordinate values */ CoordinateSequence(std::size_t size, bool hasz, bool hasm, bool initialize = true); + /** + * Create a CoordinateSequence from an externally-owned buffer + * of packed Coordinates. If Coordinates are added to the CoordinateSequence + * or the CoordinateSequence requires repacking, the values will + * be copied into a new buffer owned by this CoordinateSeuqnce. + * Code using a CoordinateSequence constructed in this way must not + * attempt to access references to coordinates with dimensions that + * are not actually stored in the sequence. + * + * @param buf buffer of interleaved coordinates + * @param size number of coordinates in the buffer + * @param hasz true if the buffer has z values + * @param hasm true if the buffer has m values + */ + CoordinateSequence(double* buf, std::size_t size, bool hasz, bool hasm); + /** * Create a CoordinateSequence from a list of XYZ coordinates. * Code using the sequence may only access references to CoordinateXY @@ -688,7 +705,7 @@ class GEOS_DLL CoordinateSequence { } private: - std::vector m_vect; // Vector to store values + geos::util::Vector m_vect; // Vector to store values uint8_t m_stride; // Stride of stored values, corresponding to underlying type diff --git a/include/geos/util/Vector.h b/include/geos/util/Vector.h new file mode 100644 index 0000000000..a29c352c21 --- /dev/null +++ b/include/geos/util/Vector.h @@ -0,0 +1,236 @@ +#pragma once + +#include +#include +#include + +namespace geos { +namespace util { + +template +class Vector { + +public: + Vector() : + m_buf(nullptr), + m_capacity(0), + m_size(0) + {} + + Vector(std::size_t sz) : + m_buf(nullptr), + m_capacity(0), + m_size(static_cast(0)) + { + resize(sz); + } + + Vector(std::size_t sz, T* buf) : + m_buf(buf), + m_capacity(0), + m_size(static_cast(sz)) + {} + + Vector(const Vector& other) : Vector() { + if (!other.empty()) { + m_buf = make_space(nullptr, other.size()); + std::memcpy(m_buf, other.m_buf, other.size()*sizeof(T)); + } + } + + Vector& operator=(const Vector& other) { + clear(); + + if (!other.empty()) { + m_buf = make_space(nullptr, other.size()); + std::memcpy(m_buf, other.m_buf, other.size()*sizeof(T)); + } + + return *this; + } + + Vector(Vector&& other) : + m_buf(other.m_buf), + m_capacity(other.m_capacity), + m_size(other.m_size) + { + other.m_buf = nullptr; + other.m_capacity = 0; + other.m_size = 0; + } + + Vector& operator=(Vector&& other) { + if (owned()) { + delete[] m_buf; + } + + m_buf = other.m_buf; + m_capacity = other.m_capacity; + m_size = other.m_size; + other.m_buf = nullptr; + other.m_capacity = 0; + other.m_size = 0; + + return *this; + } + + ~Vector() { + if (owned()) { + delete[] m_buf; + } + } + + void push_back(T item) { + growIfNeeded(1); + assert(size() < capacity()); + m_buf[m_size++] = item; + } + + void pop_back() { + assert(size() > 0); + m_size--; + } + + T* make_space(T* pos, std::size_t n) { + auto loc = pos == nullptr ? 0 : pos - begin(); + growIfNeeded(n); + pos = begin() + loc; + + if (pos != end()) { + auto num_to_move = static_cast(end() - pos); + std::memmove(pos + n, pos, num_to_move*sizeof(T)); + } + m_size += static_cast(n); + + return pos; + } + + void insert(T* pos, std::size_t n, const T& value) { + pos = make_space(pos, n); + std::fill(pos, pos + n, value); + } + + template + void insert(T* pos, Iter from, Iter to) { + auto n = static_cast(to - from); + + if (from >= begin() && from < end()) { + // from and to may be invalidated + auto from_n = from - begin(); + auto to_n = to - begin(); + + pos = make_space(pos, n); + + from = begin() + from_n; + to = begin() + to_n; + } else { + pos = make_space(pos, n); + } + + std::copy(from, to, pos); + } + + template + void assign(Iter from, Iter to) { + assert(static_cast(to - from) <= size()); + std::copy(from, to, begin()); + } + + void reserve(std::size_t sz) { + if (sz <= capacity()) { + return; + } + + T* tmp = sz > 0 ? new T[sz] : nullptr; + if (tmp && !empty()) { + std::memcpy(tmp, m_buf, m_size * sizeof(T)); + } + if (owned()) { + delete[] m_buf; + } + m_buf = tmp; + m_capacity = static_cast(sz); + } + + void resize(std::size_t sz) { + reserve(sz); + m_size = static_cast(sz); + } + + void clear() { + m_size = 0; + } + + std::size_t capacity() const { + return m_capacity; + } + + std::size_t size() const { + return m_size; + } + + bool empty() const { + return m_size == 0; + } + + bool owned() const { + return data() == nullptr || !(capacity() == 0 && size() > 0); + } + + const T& operator[](std::size_t i) const { + return *(data() + i); + } + + T& operator[](std::size_t i) { + return *(data() + i); + } + + T* release() { + m_capacity = 0; + return m_buf; + } + + T* data() { + return m_buf; + } + + const T* data() const { + return m_buf; + } + + T* begin() { + return data(); + }; + + T* end() { + return data() + size(); + } + + const T* begin() const { + return data(); + }; + + const T* end() const { + return data() + size(); + } + +private: + + void growIfNeeded(std::size_t num_to_add) { + if (size() + num_to_add > capacity()) { + auto new_capacity = capacity() == 0 ? + std::max(size() + num_to_add, static_cast(4)) : + static_cast(static_cast(capacity()) * 1.5); + new_capacity = std::max(new_capacity, capacity() + num_to_add); + reserve(new_capacity); + } + } + + T* m_buf; + std::uint32_t m_capacity; + std::uint32_t m_size; +}; + + +} +} diff --git a/src/geom/CoordinateSequence.cpp b/src/geom/CoordinateSequence.cpp index d0d79d8b46..a7ebb82431 100644 --- a/src/geom/CoordinateSequence.cpp +++ b/src/geom/CoordinateSequence.cpp @@ -65,6 +65,33 @@ CoordinateSequence::CoordinateSequence(std::size_t sz, bool hasz, bool hasm, boo } } +CoordinateSequence::CoordinateSequence(double* buf, std::size_t sz, bool hasz, bool hasm) : + m_vect(sz * (2u + hasm + hasz), buf), + m_stride(static_cast(2 + hasm + hasz)), + m_hasdim(true), + m_hasz(hasz), + m_hasm(hasm) +{ + #ifdef GEOS_COORDSEQ_PADZ + // Copy the external buffer into an internally-owned buffer + // with z-padding. + auto typ = getCoordinateType(); + if (typ == CoordinateType::XY) { + CoordinateSequence dst(sz, false, false); + for (std::size_t i =0; i < size(); i++) { + dst.setAt(getAt(i), i); + } + *this = std::move(dst); + } else if (typ == CoordinateType::XYM) { + CoordinateSequence dst(sz, false, true); + for (std::size_t i =0; i < size(); i++) { + dst.setAt(getAt(i), i); + } + *this = std::move(dst); + } + #endif +} + CoordinateSequence::CoordinateSequence(std::size_t sz, std::size_t dim) : m_vect(sz * 3), m_stride(3u), @@ -124,7 +151,7 @@ CoordinateSequence::CoordinateSequence(const std::initializer_list -void fillVector(std::vector & v) +void fillVector(util::Vector & v) { const T c; T* from = reinterpret_cast(v.data()); @@ -148,8 +175,8 @@ CoordinateSequence::add(const CoordinateSequence& cs, std::size_t from, std::siz { if (cs.stride() == stride() && cs.hasM() == cs.hasM()) { m_vect.insert(m_vect.end(), - std::next(cs.m_vect.cbegin(), static_cast(from * stride())), - std::next(cs.m_vect.cbegin(), static_cast((to + 1u)*stride()))); + std::next(cs.m_vect.begin(), static_cast(from * stride())), + std::next(cs.m_vect.begin(), static_cast((to + 1u)*stride()))); } else { std::size_t pos = size(); make_space(pos, to - from + 1); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index f29a52cf77..552944aba9 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -20,7 +20,9 @@ target_include_directories(test_geos_unit PRIVATE $) -add_executable(test_cs ${CMAKE_CURRENT_LIST_DIR}/geom/CoordinateSequenceTest.cpp +add_executable(test_cs + ${CMAKE_CURRENT_LIST_DIR}/geom/CoordinateSequenceTest.cpp + ${CMAKE_CURRENT_LIST_DIR}/util/VectorTest.cpp ${CMAKE_CURRENT_LIST_DIR}/geos_unit.cpp ${CMAKE_CURRENT_LIST_DIR}/../../src/geom/Coordinate.cpp ${CMAKE_CURRENT_LIST_DIR}/../../src/io/Unload.cpp diff --git a/tests/unit/capi/GEOSCoordSeqTest.cpp b/tests/unit/capi/GEOSCoordSeqTest.cpp index b4a000675e..13c9c7ae16 100644 --- a/tests/unit/capi/GEOSCoordSeqTest.cpp +++ b/tests/unit/capi/GEOSCoordSeqTest.cpp @@ -638,5 +638,28 @@ void object::test<17>() ensure_equals("w2", out2[7], values[7]); } +// Test creation from external buffer +template<> +template<> +void object::test<18>() +{ + std::vector values{1, 2, 3, 4, 5, 6}; + + cs_ = GEOSCoordSeq_createFromBuffer(values.data(), (unsigned int) values.size(), true, false); + + unsigned int dim_out; + ensure(GEOSCoordSeq_getDimensions(cs_, &dim_out)); + ensure_equals(dim_out, 3u); + + // Check first coordinate + values[2] = 999; + double x, y, z; + ensure(GEOSCoordSeq_getXYZ(cs_, 0, &x, &y, &z)); + ensure_equals(x, 1); + ensure_equals(y, 2); + ensure_equals(z, 999); +} + + } // namespace tut diff --git a/tests/unit/geom/CoordinateSequenceTest.cpp b/tests/unit/geom/CoordinateSequenceTest.cpp index 5c955fad84..a0e80870dc 100644 --- a/tests/unit/geom/CoordinateSequenceTest.cpp +++ b/tests/unit/geom/CoordinateSequenceTest.cpp @@ -21,6 +21,8 @@ using geos::geom::CoordinateXYM; using geos::geom::CoordinateXYZM; using geos::geom::CoordinateSequence; +constexpr const std::size_t MAX_NUMBER_OF_TESTS = 100; + namespace tut { // // Test Group @@ -48,7 +50,7 @@ struct test_coordinatearraysequence_data { }; }; -typedef test_group group; +typedef test_group group; typedef group::object object; group test_coordinatearraysequence_group("geos::geom::CoordinateSequence"); @@ -982,11 +984,11 @@ void object::test<35> seq.closeRing(); - ensure_equals(seq.size(), 5u); - ensure(seq.isRing()); + ensure_equals("size() after first closeRing()" ,seq.size(), 5u); + ensure("isRing()", seq.isRing()); seq.closeRing(); - ensure_equals(seq.size(), 5u); + ensure_equals("size() after second closeRing()", seq.size(), 5u); } // test initializer_list constructor @@ -1309,4 +1311,48 @@ void object::test<50> ensure_equals(data_ptr, seq2.data()); } +// Test creation from external buffer (3D) +template<> +template<> +void object::test<51> +() +{ + double vals[] = {1, 2, 3, 4, 5, 6}; + + CoordinateSequence seq(vals, 2, true, false); + + ensure_equals(seq.size(), 2u); + ensure(seq.getAt(0).equals3D(Coordinate(1, 2, 3))); + ensure(seq.getAt(1).equals3D(Coordinate(4, 5, 6))); + ensure_equals(seq.data(), vals); + + seq.add(Coordinate(7, 8, 9)); + ensure_equals(seq.size(), 3u); + ensure(seq.getAt(0).equals3D(Coordinate(1, 2, 3))); + ensure(seq.getAt(1).equals3D(Coordinate(4, 5, 6))); + ensure(seq.getAt(2).equals3D(Coordinate(7, 8, 9))); + ensure(seq.data() != vals); +} + +// Test creation from external buffer (2D) +template<> +template<> +void object::test<52> +() +{ + double vals[] = {1, 2, 3, 4}; + + CoordinateSequence seq(vals, 2, false, false); + + ensure_equals(seq.size(), 2u); + ensure_equals(seq.getAt(0), Coordinate(1, 2)); + ensure_equals(seq.getAt(1), Coordinate(3, 4)); + + seq.add(Coordinate(5, 6)); + ensure_equals(seq.size(), 3u); + ensure_equals(seq.getAt(0), Coordinate(1, 2)); + ensure_equals(seq.getAt(1), Coordinate(3, 4)); + ensure_equals(seq.getAt(2), Coordinate(5, 6)); +} + } // namespace tut diff --git a/tests/unit/util/VectorTest.cpp b/tests/unit/util/VectorTest.cpp new file mode 100644 index 0000000000..428b33792d --- /dev/null +++ b/tests/unit/util/VectorTest.cpp @@ -0,0 +1,372 @@ +// +// Test Suite for geos::util::Vector class. + +// tut +#include +// geos +#include + +#include + +using geos::util::Vector; + +namespace tut { +// +// Test Group +// + +template +std::ostream& operator<<(std::ostream& os, const Vector& v) { + for (const auto& item : v) { + os << item << " "; + } + os << std::endl; + return os; +} + +// Common data used in test cases. +struct test_vector_data { +}; + + +typedef test_group group; +typedef group::object object; + +group test_vector_group("geos::util::Vector"); + +// +// Test Cases +// + +// Construct empty +template<> +template<> +void object::test<1>() +{ + Vector v; + + ensure_equals(v.size(), 0u); + ensure_equals(v.capacity(), 0u); + ensure("empty()", v.empty()); + ensure("owned()", v.owned()); +} + +// Construct fixed-size +template<> +template<> +void object::test<2>() +{ + Vector v(5); + + ensure_equals(v.size(), 5u); + ensure_equals(v.capacity(), 5u); + ensure("empty()", !v.empty()); + ensure("owned()", v.owned()); +} + +// Construct fixed-size (empty) +template<> +template<> +void object::test<3>() +{ + Vector v(0); + + ensure_equals(v.size(), 0u); + ensure_equals(v.capacity(), 0u); + ensure("empty()", v.empty()); + ensure("owned()", v.owned()); +} + +// Construct from external +template<> +template<> +void object::test<4>() +{ + int items[] = {1, 2, 3}; + + Vector v(3, items); + + ensure_equals(v.size(), 3u); + ensure_equals(v.capacity(), 0u); + ensure("empty()", !v.empty()); + ensure("owned()", !v.owned()); +} + +// Construct from external nullptr +template<> +template<> +void object::test<5>() +{ + Vector v(0, nullptr); + + ensure_equals(v.size(), 0u); + ensure_equals(v.capacity(), 0u); + ensure("empty()", v.empty()); + ensure("owned()", v.owned()); +} + +// Add single items +template<> +template<> +void object::test<6>() +{ + Vector v; + for (int i = 0; i < 100; i++) { + v.push_back(i); + } + + ensure_equals(v.size(), 100u); + ensure("capacity()", v.capacity() >= 100); + ensure("empty()", !v.empty()); + ensure("owned()", v.owned()); + + for (std::size_t i = 0; i < v.size(); i++) { + ensure_equals(v[i], static_cast(i)); + } +} + +// Add single items, starting from external buffer +template<> +template<> +void object::test<7>() { + int items[] = {0, 1, 2, 3, 4}; + Vector v(5, items); + + for (int i = 5; i < 20; i++) { + v.push_back(i); + } + + ensure_equals(v.size(), 20u); + ensure("capacity()", v.capacity() >= 20); + ensure("empty()", !v.empty()); + ensure("owned()", v.owned()); + + for (std::size_t i = 0; i < v.size(); i++) { + ensure_equals(v[i], static_cast(i)); + } +} + +// Add multiple items at end +template<> +template<> +void object::test<8>() { + Vector v; + for (int i = 0; i < 10; i++) { + v.push_back(i); + } + + v.insert(v.end(), 5u, 999); + + ensure_equals(v.size(), 15u); + ensure("capacity()", v.capacity() >= 15); + ensure("empty()", !v.empty()); + ensure("owned()", v.owned()); + + for (std::size_t i = 0; i < v.size(); i++) { + if (i < 10) { + ensure_equals(v[i], static_cast(i)); + } else { + ensure_equals(v[i], 999); + } + } +} + +// Add multiple items in middle +template<> +template<> +void object::test<9>() { + int items[] = {0, 1, 2}; + + Vector v(3u, items); + + v.insert(v.begin() + 1, 2u, 999); + + ensure_equals(v.size(), 5u); + ensure("capacity()", v.capacity() >= 5); + ensure("empty()", !v.empty()); + ensure("owned()", v.owned()); + + ensure_equals(v[0], 0); + ensure_equals(v[1], 999); + ensure_equals(v[2], 999); + ensure_equals(v[3], 1); + ensure_equals(v[4], 2); +} + +// Copy contructor (empty) +template<> +template<> +void object::test<10>() { + Vector v1; + Vector v2(v1); +} + +// Copy constructor (owned, non-empty) +template<> +template<> +void object::test<11>() { + Vector v1; + v1.push_back(1); + v1.push_back(2); + + Vector v2(v1); + + ensure_equals(v1.size(), v2.size()); + ensure(v1.data() != v2.data()); +} + +// Copy contructor (non-owned) +template<> +template<> +void object::test<12>() { + int items[] = {0, 1, 2}; + Vector v1(3u, items); + + Vector v2(v1); + + ensure_equals(v1.size(), v2.size()); + ensure(v1.data() != v2.data()); + ensure("owned()", v2.owned()); +} + +// Copy assignment (empty) +template<> +template<> +void object::test<13>() { + Vector v1; + Vector v2(5); + v2 = v1; + + ensure("empty()", v2.empty()); +} + +// Copy assignment (owned, non-empty) +template<> +template<> +void object::test<14>() { + Vector v1; + v1.push_back(1); + v1.push_back(2); + + Vector v2(5); + v2 = v1; + + ensure_equals(v1.size(), v2.size()); + ensure(v1.data() != v2.data()); +} + +// Copy assignment (non-owned) +template<> +template<> +void object::test<15>() { + int items[] = {0, 1, 2}; + Vector v1(3u, items); + + Vector v2(5); + v2 = v1; + + ensure_equals(v1.size(), v2.size()); + ensure(v1.data() != v2.data()); + ensure("owned()", v2.owned()); +} + +// Move construction (empty) +template<> +template<> +void object::test<16>() { + Vector v1; + Vector v2(std::move(v1)); + + ensure("empty()", v2.empty()); +} + +// Move construction (owned, non-empty) +template<> +template<> +void object::test<17>() { + Vector v1; + v1.push_back(1); + v1.push_back(2); + + auto ptr = v1.data(); + + Vector v2(std::move(v1)); + + ensure_equals(v2.size(), 2u); + ensure("owned()", v2.owned()); + ensure_equals(v2.data(), ptr); +} + +// Move construction (non-owned) +template<> +template<> +void object::test<18>() { + int items[] = {0, 1, 2}; + Vector v1(3u, items); + Vector v2(std::move(v1)); + + ensure_equals(v2.size(), 3u); + ensure("owned()", !v2.owned()); + ensure_equals(v2.data(), items); +} + +// Move assignment (empty) +template<> +template<> +void object::test<19>() { + Vector v1; + Vector v2(5); + + v2 = std::move(v1); + + ensure("empty()", v2.empty()); +} + +// Move assignment (owned, non-empty) +template<> +template<> +void object::test<20>() { + Vector v1; + v1.push_back(1); + v1.push_back(2); + + auto ptr = v1.data(); + + Vector v2(5); + v2 = std::move(v1); + + ensure_equals(v2.size(), 2u); + ensure("owned()", v2.owned()); + ensure_equals(v2.data(), ptr); +} + +// Move assignment (non-owned) +template<> +template<> +void object::test<21>() { + int items[] = {0, 1, 2}; + Vector v1(3u, items); + Vector v2(5); + + v2 = std::move(v1); + + ensure_equals(v2.size(), 3u); + ensure("owned()", !v2.owned()); + ensure_equals(v2.data(), items); +} + +// No invalidation if we are within existing capacity +template<> +template<> +void object::test<22>() { + Vector v; + v.reserve(8); + + auto ptr = v.data(); + v.insert(v.begin(), 8u, 999); + + ensure_equals(v.data(), ptr); +} + + +}