#include #include #include // All implementations in this class use ints insteads of size_t because size_t // is interely slow compared to ints. C++20 also adds new methods to call for // signed container sizes. Several online resources also suggest to use unsigned // types mostly for bit operations and nothing else. // This struct is used in the automated tests at the end of the file. // It counts all instances with different numbers to ensure each object is // constructed and destructed the same amount of times. // A also has a non trivial constructor to test the vector struct A { static int instances; int a; A(int a) : a(a) { // std::cout << "construct A " << a << "\n"; instances += a; } A(const A& other) : a(other.a) { // std::cout << "copy construct A " << a << "\n"; instances += a; } A(A&& other) : a(other.a) { // std::cout << "move construct A " << a << "\n"; instances += a; } A& operator=(const A& other) { instances -= a; a = other.a; instances += a; // std::cout << "copy assignment A " << a << "\n"; return *this; } A& operator=(A&& other) { instances -= a; a = other.a; instances += a; // std::cout << "move assignment A " << a << "\n"; return *this; } ~A() { // std::cout << "destruct A " << a << "\n"; instances -= a; } }; int A::instances = 0; template class Vector { T* data; int capacity; int elements; public: Vector() : data(nullptr), capacity(0), elements(0) { } Vector(int n, const T& t) : Vector() { for(int i = 0; i < n; i++) { push_back(t); } } Vector(int n) : Vector(n, T()) { } ~Vector() { // placement new needs explicit destructor calling for(int i = 0; i < elements; i++) { data[i].~T(); } // casting this pointer not back to its origin type yields a crash delete[] reinterpret_cast(data); } // allocate new storage for copies Vector(const Vector& other) : data(allocate(other.capacity)), capacity(other.capacity), elements(other.elements) { // copy data into new storage for(int i = 0; i < elements; i++) { new(data + i) T(other.data[i]); } } // this handles copy and move assigment // copy: replace current resources with other made by the copy constructor // move: replace current resources with other made by the move constructor // other gets the old data and is destroyed by the destructor after going // out of scope Vector& operator=(Vector other) { swap(*this, other); return *this; } // construct a default vector so the other vector gets valid values from the // swap Vector(Vector&& other) : Vector() { swap(*this, other); } void reserve(int size) { if(size <= capacity) { return; } Vector v; v.capacity = size; v.data = allocate(size); for(int i = 0; i < elements; i++) { v.push_back(std::move(data[i])); } swap(*this, v); } void resize(int size, const T& t) { // elements will be equal to size after this call but not the capacity if(size > elements) { // fill until the given size is reached for(int i = elements; i < size; i++) { push_back(t); } } else if(size < elements) { // remove objects until the size matches for(int i = size; i < elements; i++) { data[i].~T(); } elements = size; } } void resize(int size) { resize(size, T()); } void push_back(const T& t) { ensureCapacity(); new(data + elements++) T(t); } void push_back(T&& t) { ensureCapacity(); // && would be lost without std::move new(data + elements++) T(std::move(t)); } T& operator[](int index) { return data[index]; } const T& operator[](int index) const { return data[index]; } T& at(int index) { // std states "at" is [] with range check assert(index >= 0 && index < elements); return (*this)[index]; } const T& at(int index) const { // std states "at" is [] with range check assert(index >= 0 && index < elements); return (*this)[index]; } int size() const { return elements; } T* begin() { return data; } T* end() { return data + elements; } const T* begin() const { return data; } const T* end() const { return data + elements; } void erase(T* from, T* to) { T* remove = from; T* move = to; T* stop = end(); // fill the hole by moving following objects as long as possible while(move != stop) { *remove = std::move(*move); remove++; move++; } // remove left over objects while(remove != stop) { remove->~T(); remove++; elements--; } } void erase(T* start) { erase(start, start + 1); } T* as_array() { return begin(); } const T* as_array() const { return begin(); } void erase_by_swap(int index) { elements--; if(index != elements) { data[index] = std::move(data[elements]); } data[elements].~T(); } int length() const { return capacity; } private: static T* allocate(int length) { return reinterpret_cast(new char[sizeof(T) * length]); } static void swap(Vector& a, Vector& b) { std::swap(a.data, b.data); std::swap(a.capacity, b.capacity); std::swap(a.elements, b.elements); } void ensureCapacity() { if(elements >= capacity) { // doubling the size amortizes costs reserve(capacity == 0 ? 1 : capacity * 2); } } }; void printError(int number) { std::cout << "\033[0;31mError " << number << "\033[0m\n"; } // V is either std::vector or Vector to test for complete same behaviour in both // implementations template void test() { { const int elements = 2; V v; for(int i = 0; i < elements; i++) { v.push_back(A(i)); } const V& cv = v; for(int i = 0; i < elements; i++) { if(v[i].a != i || cv[i].a != i || v.at(i).a != i || cv.at(i).a != i) { printError(1); } } if(v.size() != elements) { printError(2); } } { V v1; v1.push_back(A(10)); V v2 = v1; V v3; v3.push_back(A(20)); v3 = v1; if(v1[0].a != 10 || v2[0].a != 10 || v3[0].a != 10) { printError(3); } V v4 = std::move(v1); V v5(std::move(v2)); V v6; v6 = std::move(v3); if(v1.size() != 0 || v2.size() != 0 || v3.size() != 0 || v4.size() != 1 || v5.size() != 1 || v6.size() != 1) { printError(4); } } { V v; v.resize(1, A(8)); v.resize(3, A(9)); if(v.size() != 3 || v[0].a != 8 || v[1].a != 9 || v[2].a != 9) { printError(5); } v.resize(1, A(10)); if(v.size() != 1 || v[0].a != 8) { printError(6); } } { std::vector v1; V v2; for(int i = 0; i < 200; i++) { v1.push_back(A(i)); v2.push_back(A(i)); } v1.erase(v1.begin()); v1.erase(v1.begin() + 4, v1.begin() + 8); v1.erase(v1.begin() + 30, v1.begin() + 56); v2.erase(v2.begin()); v2.erase(v2.begin() + 4, v2.begin() + 8); v2.erase(v2.begin() + 30, v2.begin() + 56); if(static_cast(v1.size()) != static_cast(v2.size())) { printError(100); } else { for(unsigned int i = 0; i < v1.size(); i++) { if(v1[i].a != v2[i].a) { printError(200); } } } } if(A::instances != 0) { std::cout << "object counter is not 0: " << A::instances << "\n"; } } void test() { { Vector v; v.push_back(1); v.push_back(2); v.push_back(3); v.erase_by_swap(1); if(v.size() != 2 || v[0].a != 1 || v[1].a != 3) { printError(9); } v.erase_by_swap(1); if(v.size() != 1 || v[0].a != 1) { printError(10); } v.erase_by_swap(0); if(v.size() != 0) { printError(11); } } if(A::instances != 0) { std::cout << "object counter is not 0: " << A::instances << "\n"; } } int main() { test>(); test(); std::cout << "--------------------------\n"; test>(); Vector test(3); }