gedeng/Vector.h

202 lines
6.3 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) 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()));
}
}
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);
delete[] data;
data = new_data;
}
};