#pragma once #include #include #include template class Vector { public: // Constructor // If the needed capacity is known, it can be passed; other wise, a default // capacity is reserved. Vector(unsigned int capacity = 10) : capacity(capacity), element_count(0), data((T *)::operator new(capacity * sizeof(T))) { } // Copy Constructor Vector(const Vector &other) : capacity(other.capacity), element_count(other.element_count), data((T *)::operator new(capacity * sizeof(T))) { // `std::copy` is used because it is more flexible than `std::memcpy`, // and the compiler will replace it with `memcpy` anyway if appropriate, // so there is no performance loss. std::copy(other.data, other.data + element_count, data); } // Move Constructor using the copy-and-swap-idiom Vector(Vector &&other) : data(nullptr) { swap(*this, other); } // Copy Assignment Operator using the copy-and-swap-idiom // Since this takes a value rather than a const reference due to // copy-and-swap, this is also the Move Assignment Operator. // This works because we pass-by-value, causing the copy constructor (which // does the actual copying) to be called, and then we swap the resulting // temporary into `this`. Vector &operator=(Vector other) { swap(*this, other); return *this; } // Destructor ~Vector() { for (unsigned int i = 0; i < element_count; i++) { data[i].~T(); } ::operator delete(data, capacity * sizeof(T)); } // Bracket Operator for accessing elements T &operator[](unsigned int position) const { return at(position); } // Equals Operator: Returns true if the number of elements is identical and // each of these elements are equal bool operator==(const Vector &other) const { if (size() != other.size()) return false; for (unsigned int i = 0; i < size(); i++) { if (at(i) != other[i]) { return false; } } return true; } // Swap function for the copy-and-swap idiom. // See also: // https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom // The `friend` keyword is used so it is found through ADL. friend void swap(Vector &first, Vector &second) { // Enable ADL (good practice) using std::swap; swap(first.capacity, second.capacity); swap(first.element_count, second.element_count); swap(first.data, second.data); } void push_back(const T &element) { if (new_size_exceeds_capacity(size() + 1)) { reallocate(get_capacity_for_size(size() + 1)); } // Use placement new to directly use pre-allocated memory new (data + element_count) T(element); element_count++; } void erase(unsigned int position) { assert(position < element_count); // Call the destructor on the given element data[position].~T(); // Copy the other elements forwards std::copy_backward(data + position + 1, data + element_count, data + element_count - 1); element_count--; if (is_using_excessive_memory()) { reallocate(get_capacity_for_size(size())); } } // Erase an element by swapping the previously last element to its place void erase_by_swap(unsigned int position) { assert(position < element_count); // Swap the last element to this position std::swap(data[position], data[element_count - 1]); // Delete the previous element at this position (now last) data[element_count - 1].~T(); element_count--; } T *as_array() const { return data; } // Returns the number of elements in the vector, regardless of the actually // reserved memory. unsigned int size() const { return element_count; } // Returns the number of elements which could be in the vector using the // currently allocated memory, regardless of how many of these slots are // currently actually used. unsigned int length() const { return capacity; } // Returns a reference to the element at the given position T &at(unsigned int position) const { assert(position < element_count); return data[position]; } // Expand the capacity of the vector by at least the given addition void reserve(unsigned int addition) { capacity += addition; reallocate(capacity); } // Resize the size of the vector to the given new_size // If this decreases the size, some elements are deleted // If this increases the size, some default-constructed elements are added. // If no default constructor is available, a padder must be passed. void resize(unsigned int new_size, const T &padder = T()) { int difference = new_size - size(); if (difference > 0) { if (new_size_exceeds_capacity(new_size)) { reallocate(new_size); } // Add additional items for (int i = 0; i < difference; i++) { new (data + element_count + i) T(padder); } } else if (difference < 0) { // Call the destructor on all excess items for (int i = -1; i >= difference; i--) { data[element_count + i].~T(); } if (is_using_excessive_memory()) { reallocate(get_capacity_for_size(new_size)); } } element_count += difference; } private: unsigned int capacity; // Number of T objects the Vector could hold unsigned int element_count; // Number of T objects the Vector currently holds T *data; // Return a sensible capacity for the given size (including some // padding). unsigned int get_capacity_for_size(unsigned int size) const { return size * 2 + 5; } // Return true if the Vector is holding much more memory (capacity) than it // currently needs (element_count). inline bool is_using_excessive_memory() const { return 10 + element_count * 2 < capacity; } // Return true if the passed new_size would cause the Vector to exceed the // current capacity (meaning it must be expanded with `reallocate` first). inline bool new_size_exceeds_capacity(unsigned int new_size) const { return new_size > capacity; } // Allocate enough memory to hold at least new_size entries. // Note that this function doesn't validate its input for optimization // reasons! void reallocate(unsigned int new_size) { T *new_data = (T *)::operator new(new_size * sizeof(T)); std::copy(data, data + element_count, new_data); ::operator delete(data, capacity * sizeof(T)); data = new_data; } };