192 lines
5.8 KiB
C++
192 lines
5.8 KiB
C++
#pragma once
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <iostream>
|
|
|
|
template <class T>
|
|
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() {
|
|
delete[] data;
|
|
}
|
|
|
|
// 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) {
|
|
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()));
|
|
}
|
|
}
|
|
|
|
// 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
|
|
void resize(unsigned int new_size) {
|
|
int difference = new_size - size();
|
|
|
|
if (difference > 0) {
|
|
if (new_size_exceeds_capacity(new_size)) {
|
|
reallocate(new_size);
|
|
}
|
|
|
|
// Add additional default-constructed items
|
|
for (int i = 0; i < difference; i++) {
|
|
data[element_count + i] = T();
|
|
}
|
|
} 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;
|
|
unsigned int element_count;
|
|
T *data;
|
|
|
|
bool is_using_excessive_memory() {
|
|
return 10 + element_count * 2 < capacity;
|
|
}
|
|
|
|
bool new_size_exceeds_capacity(unsigned int new_size) {
|
|
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);
|
|
delete[] data;
|
|
|
|
data = new_data;
|
|
}
|
|
|
|
// Return a sensible capacity for the given size (including some
|
|
// padding).
|
|
unsigned int get_capacity_for_size(unsigned int size) {
|
|
return size * 2 + 5;
|
|
}
|
|
};
|