Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #pragma once
- template<typename Vector>
- class VectorIterator
- {
- public:
- using ValueType = typename Vector::ValueType;
- using PointerType = ValueType*;
- using ReferenceType = ValueType&;
- public:
- VectorIterator(PointerType ptr)
- : m_Ptr(ptr) {}
- VectorIterator& operator++() // Prefix operator
- {
- m_Ptr++; // increments actual pointer by correct amount of bytes because it increments by the size of the type
- return *this; // vector iterator ref
- }
- VectorIterator operator++(int) // Postfix operator differentiated by taking in an int vs nothing for other
- {
- VectorIterator iterator = *this; // Returns copy of iterator since we're not supposed to modify obj in place
- ++(*this); // Calling prefix operator here
- return iterator;
- }
- VectorIterator& operator--() // Prefix operator
- {
- m_Ptr--; // increments actual pointer by correct amount of bytes because it increments by the size of the type
- return *this; // vector iterator ref
- }
- VectorIterator operator--(int) // Postfix operator differentiated by taking in an int vs nothing for other
- {
- VectorIterator iterator = *this; // Returns copy of iterator since we're not supposed to modify obj in place
- --(*this); // Calling prefix operator here
- return iterator;
- }
- ReferenceType operator[](int index)
- {
- return *(m_Ptr + index);
- }
- PointerType operator->()
- {
- return m_Ptr; // current position of the iterator
- }
- ReferenceType operator*() // dereference operator
- {
- return *m_Ptr; // return dereferenced pointer
- }
- bool operator==(const VectorIterator& other) const
- {
- return m_Ptr == other.m_Ptr;
- }
- bool operator!=(const VectorIterator& other) const
- {
- return !(*this == other);
- }
- private:
- PointerType m_Ptr; // Don't have to assign value since doing this in the constructor
- };
- template<typename T>
- class Vector
- {
- public:
- using ValueType = T;
- using Iterator = VectorIterator<Vector<T>>;
- public:
- Vector()
- {
- // allocate memory for us to store 2 elements
- ReAlloc(2);
- }
- ~Vector() // Won't go and call the destructors, Clear does so
- {
- Clear();
- ::operator delete(m_Data, m_Capacity * sizeof(T));
- }
- void PushBack(const T& value)
- {
- // If we attempt to push back an element and there's no room, ReAlloc inside is called
- if (m_Size >= m_Capacity)
- {
- ReAlloc(m_Capacity + m_Capacity / 2); // Growing by 1.5x (50%) each time
- }
- // Basic way of moving things into array
- m_Data[m_Size] = value;
- m_Size++;
- }
- void PushBack(T&& value) // Temporary value (rvalue), does moving instead of copying like one above
- {
- if (m_Size >= m_Capacity)
- {
- ReAlloc(m_Capacity + m_Capacity / 2);
- }
- // Once enter a function like this, RValue ends up becoming an LValue, so we use std move to still treat it like an RValue when moving
- m_Data[m_Size] = std::move(value);
- m_Size++;
- }
- template<typename... Args>
- T& EmplaceBack(Args&&... args)
- {
- if (m_Size >= m_Capacity)
- ReAlloc(m_Capacity + m_Capacity / 2);
- // Placement New for constructing objects in place
- new(&m_Data[m_Size]) T(std::forward<Args>(args)...); // Forward all arguments to constructor, triple dot to unpack the arguments
- return m_Data[m_Size++];
- }
- void PopBack()
- {
- if (m_Size > 0)
- {
- m_Size--;
- m_Data[m_Size].~T();
- }
- }
- void Clear() // Will go through and call all the destructors
- {
- for (size_t i = 0; i < m_Size; i++)
- m_Data[i].~T();
- m_Size = 0;
- }
- const T& operator[](size_t index) const
- {
- if (index > m_Size) // If trying to access index outside bounds of array
- {
- // assert
- }
- return m_Data[index];
- }
- T& operator[](size_t index)
- {
- if (index > m_Size) // If trying to access index outside bounds of array
- {
- // assert
- }
- return m_Data[index];
- }
- size_t Size() const { return m_Size; } // Function that returns our size
- Iterator begin()
- {
- return Iterator(m_Data); // beginning of data block is m_Data
- }
- Iterator end()
- {
- return Iterator(m_Data + m_Size); // Sends it to first byte of memory outside m_Data allocation
- }
- private:
- void ReAlloc(size_t newCapacity)
- {
- // Steps in order
- // 1. Needs to allocate a new block of memory
- // 2. Copy/move old elements into new block, preferably move
- // 3. Delete old block
- // Want to use versions of new and delete that don't call the constructor or destructor
- T* newBlock = (T*)::operator new(newCapacity * sizeof(T)); // Raw pointers preferred when doing something low level like this
- // If downsizing instead of growing array, ensures array isn't overflowed -> done by only copying up to newCapacity size of elements
- if (newCapacity < m_Size)
- m_Size = newCapacity;
- for (size_t i = 0; i < m_Size; i++)
- new (&newBlock[i]) T(std::move(m_Data[i]));
- // Line above calls constructor properly so that current newBlock string that has tidy_dellocate() called on it actually exists
- // If constructor isn't called properly, string won't exist and we'll crash.
- // Can't use newBlock[i] = std::move(m_Data[i]); since assignment operator (=) requires newBlock[i] to exist
- // Use of placement new fixes this
- for (size_t i = 0; i < m_Size; i++)
- m_Data[i].~T();
- ::operator delete(m_Data, m_Capacity * sizeof(T)); // Deletes old block
- m_Data = newBlock; // Assign data to new block
- m_Capacity = newCapacity; // Make sure our capacity matches new capacity
- }
- private:
- T* m_Data = nullptr; // Pointer to whatever our type is, nullptr as default so code is easier to debug
- // Need those so you're not reallocating memory as often
- size_t m_Size = 0; // Number of elements in our vector
- size_t m_Capacity = 0; // How much memory have we allocated, how much could we store without having to reallocate
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement