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\$