Implementation of the vector
class in C++ for learning purposes. Tried to implement interesting parts from the API shown on cppreference.com, but some elements like custom allocator support are missing.
template <typename T>
class Vector {
public:
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
// Iterator support
using iterator = pointer;
using const_iterator = const pointer;
Vector(): data{nullptr}, m_size{0}, m_capacity{0} {}
explicit Vector(size_type capacity) : Vector() {
resize(capacity);
}
Vector(size_type count, const_reference value): Vector() {
resize(count, value);
}
template<typename InputIt>
Vector(InputIt first, InputIt last) : Vector() {
assign(first, last);
}
Vector(std::initializer_list<T> init) : Vector() {
assign(init.begin(), init.end());
}
Vector(const Vector& other): Vector() {
assignRange(other.begin(), other.end());
}
Vector& operator=(const Vector& other) {
Vector temp = other;
swap(temp);
return *this;
}
Vector(Vector&& other) noexcept : Vector() {
swap(other);
}
Vector& operator=(Vector&& other) noexcept {
swap(other);
return *this;
}
~Vector() {
clear();
deallocate(data);
}
size_type size() const noexcept {
return m_size;
}
reference operator[](size_type index) {
return data[index];
}
void resize(size_type count, const_reference value = T()) {
if (count < m_size) {
for (size_type i = count; i < m_size; i++) {
data[i].~T();
}
} else if (count > m_size) {
if (count > m_capacity) {
reserve(count);
}
for (size_type i = m_size; i < count; i++) {
new (data + i) T(value);
}
}
m_size = count;
}
void reserve(size_type count) {
if (m_capacity < count) {
pointer newData = allocate(count);
if (m_size > 0) {
std::uninitialized_move(data, data + m_size, newData);
}
deallocate(data);
data = newData;
m_capacity = count;
}
}
void reserve_if_needed() {
if (m_size == m_capacity) {
if (m_capacity == 0) {
reserve(1);
} else {
reserve(m_capacity * 2);
}
}
}
void push_back(const_reference item) {
reserve_if_needed();
data[m_size++] = item;
}
void pop_back() {
// The standard says pop_back() on an empty vector is
// undefined behavior, so this check is possibly unnecessary
// since implementations can technically do whatever
// in case of undefined behavior?
if (m_size > 0) {
data[m_size - 1].~T();
m_size -= 1;
}
}
template<typename... Args>
void emplace_back(Args&&... args) {
reserve_if_needed();
new (data + m_size) T(std::forward<Args>(args)...);
m_size++;
}
void shrink_to_fit() {
if (m_capacity > m_size) {
pointer new_data = allocate(m_size);
if (m_size > 0) {
std::uninitialized_move(data, data + m_size, new_data);
}
deallocate(data);
data = new_data;
m_capacity = m_size;
}
}
void swap (Vector& other) noexcept {
using std::swap;
swap(data, other.data);
swap(m_size, other.m_size);
swap(m_capacity, other.m_capacity);
}
// Iterator support
iterator begin() const {
return data;
}
iterator end() const {
return data + m_size;
}
iterator insert(iterator pos, const_reference item) {
return insert(pos, 1, item);
}
iterator insert(iterator pos, size_type count, const_reference item) {
size_type index = pos - data;
size_type remaining = m_size - index;
if (m_capacity < m_size + count) {
reserve(m_size + count);
}
std::uninitialized_move(data + index, data + m_size, data + index + count);
std::uninitialized_fill(data + index, data + index + count, item);
m_size += count;
return data + index;
}
iterator erase(iterator pos) {
return erase(pos, pos + 1);
}
iterator erase(iterator first, iterator last) {
size_type n_elements = last - first;
size_type index = first - data;
for (size_type i = index; i < index + n_elements; i++) {
data[i].~T();
}
std::move(data + index + n_elements, data + m_size, data + index);
m_size -= n_elements;
return first;
}
template<typename InputIt>
void assign(InputIt first, InputIt last) {
assignRange(first, last);
}
void clear() noexcept {
for (std::size_t i = 0; i < m_size; i++) {
data[i].~T();
}
m_size = 0;
}
template<typename InputIt>
void assignRange(InputIt first, InputIt last) {
clear();
resize(std::distance(first, last));
std::uninitialized_copy(first, last, data);
}
void assign(size_type count, const_reference value) {
Vector temp(count, value);
swap(temp);
}
private:
pointer data = nullptr;
size_type m_size;
size_type m_capacity;
pointer allocate(size_type count) {
return static_cast<pointer>(::operator new(count * sizeof(value_type)));
}
void deallocate(pointer p) {
if (p != nullptr) {
::operator delete(p);
}
}
};
start
,size
,capacity
), they have (start
,end
,buff_end
) \$\endgroup\$.~T()
and then acting as if it is a valid element on which you call assignment operations. That's a bug. You gotta to use placement new to construct the object instead after deletion. I'd rewrite places where it is uses unnecessarily. Also in assignment operation it is not a good practice to consistently allocate new memory, it is inefficient for such a basic-class. You should reuse current memory if possible. \$\endgroup\$