Simple implementation of vector in C + +

Posted by neville on Sun, 06 Mar 2022 16:00:57 +0100

vector

Vectors are sequence containers that represent arrays that can be resized.

Like arrays, vectors use contiguous storage locations for their elements, which means that their elements can also be accessed using offsets on regular pointers to their elements and are as efficient as arrays. But unlike arrays, their size can change dynamically, and their storage is automatically processed by the container.

Internally, vectors use dynamically allocated arrays to store their elements. You may need to reallocate this array to increase the size when inserting new elements, which means allocating a new array and moving all elements to it. This is a relatively expensive task in terms of processing time, so the vectors are not reassigned each time an element is added to the container.

Instead, vector containers can allocate some additional storage to accommodate possible growth, so the actual capacity of the container may be greater than the strictly required storage to contain its elements (that is, its size). Libraries can implement different growth strategies to balance memory usage and reallocation, but in any case, reallocation should occur only at logarithmically increasing size intervals so that a single element can be inserted at the end of the vector and provide constant time complexity for amortization.

Therefore, compared with arrays, vectors consume more memory in exchange for the ability to manage storage and grow dynamically in an effective way.

Compared with other dynamic sequence containers (deques, list, and forward_lists), vectors access their elements very efficiently (just like arrays) and add or remove elements from their ends relatively efficiently. For operations that involve inserting or deleting elements outside the end, they perform worse than other elements, and the consistency of iterators and references is lower than lists and forward_ lists.

cpp

template <typename T>
class Vector
{
public:
    Vector() noexcept = default;
    explicit Vector(size_t n) : cap_{n}, ptr_{alloc(cap_)}
    {
        for (; len_ < n; ++len_)
        {
            construct(ptr_ + len_); //Call the default construction of T
        }
    }
    Vector(size_t n, const T &x) : cap_{n}, ptr_{alloc(cap_)}
    {
        for (; len_ < n; ++len_)
        {
            construct(ptr_ + len_, x); //Call copy construction of T
        }
    }
    Vector(const Vector &x) : cap_{x.size()}, ptr_{alloc(cap_)} //copy construction 
    {
        for (; len_ < x.size(); ++len_)
        {
            construct(ptr_ + len_, x[len_]);
        }
    }
    Vector(Vector &&x) noexcept //Mobile structure
    {
        cap_ = std::__exchange(x.cap_, 0);
        len_ = std::__exchange(x.len_, 0);
        ptr_ = std::__exchange(x.ptr_, nullptr);
    }
    Vector(std::initializer_list<T> li) : cap_{li.size()}, ptr_{alloc(cap_)} //Initialization list
    {
        for (auto &x : li)
        {
            construct(ptr_ + len_, x);
            ++len_;
        }
    }

    ~Vector() noexcept
    {
        clear();
        dealloc(ptr_);
    }

    void swap(Vector &x) noexcept
    {
        using std::swap; // ADL
        swap(cap_, x.cap_);
        swap(len_, x.len_);
        swap(ptr_, x.ptr_);
    }
    void clear() noexcept
    {
        for (; len_ > 0; --len_)
        {
            destroy(ptr_ + len_ - 1);
        }
    }

    Vector &operator=(const T &x) //copy assignment 
    {
        if (this != &x)
        {
            Vector{x}.swap(*this);
        }
        return *this;
    }
    Vector &operator=(T &&x) noexcept //Move assignment
    {
        if (this != &x)
        {
            Vector{std::move(x)}.swap(*this);
        }
        return *this;
    }
    Vector &operator=(std::initializer_list<T> li) //Initialize list assignment
    {
        Vector{li}.swap(*this);
        return *this;
    }

    void push_back(const T &x) //Copy
    {
        emplace_back(x);
    }
    void push_back(T &&x) //move
    {
        emplace_back(x);
    }
    template <typename... Args>
    void emplace_back(Args &&...args) //Direct pass constructor
    {
        if (len_ == cap_)
        {
            size_t new_cap = cap_ ? cap_ * 2 : 1; //Wait for 0 to return 1
            T *new_ptr = alloc(new_cap);
            for (size_t new_len; new_len < len_; ++new_len)
            {
                construct(new_ptr + new_len, std::move_if_noexcept(ptr_[new_len]));
            }
            cap_ = new_cap;
            ptr_ = new_ptr;
        }
        construct(ptr_ + len_, std::forward<Args>(args)...);
        ++len_;
    }
    void pop_back() noexcept
    {
        if (len_ < cap_ / 2)
        {
            size_t new_cap = cap_ / 2;
            T *new_ptr = alloc(new_cap);
            for (size_t new_len = 0; new_len < len_; ++new_len)
            {
                construct(new_ptr + new_len, std::move_if_noexcept(ptr_[new_len]));
            }
            cap_ = new_cap;
            ptr_ = new_ptr;
        }
        destroy(ptr_ + len_ - 1);
        --len_;
    }
    size_t size() const noexcept
    {
        return len_;
    }
    size_t capacity() const noexcept
    {
        return cap_;
    }
    bool empty() const noexcept
    {
        return len_ == 0;
    }

    T &operator[](size_t i)
    {
        return ptr_[i];
    }
    const T &operator[](size_t i) const
    {
        return ptr_[i];
    }

    T *begin() noexcept
    {
        return ptr_;
    }
    T *end() noexcept
    {
        return ptr_ + len_;
    }
    const T *begin() const noexcept
    {
        return ptr_;
    }
    const T *end() const noexcept
    {
        return ptr_ + len_;
    }

private:
    T *alloc(size_t n) //Allocate n memory sizes
    {
        return static_cast<T *>(::operator new(sizeof(T) * n));
    }
    void dealloc(T *p) noexcept //Free memory
    {
        ::operator delete(p);
    }
    template <typename... Args>
    void construct(T *p, Args &&...args) //Construct a T-type object on this memory
    {
        ::new (p) T(std::forward<Args>(args)...);
    }
    void destroy(T *p) noexcept
    {
        p->~T();
    }

private:
    size_t cap_{0}; //capacity
    size_t len_{0}; //Number of elements
    T *ptr_{nullptr};
};

Topics: C++ data structure STL vector