Lessons Learned from My Implementation of C++ Vector
Debugging Log of C++ Vector implementation using Array.
Original Code MyVector.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #ifndef ADVANCED_MYVECTOR_H #define ADVANCED_MYVECTOR_H #include <cstddef> using namespace std;template <typename T>class MyVector {public : MyVector (); MyVector (size_t size); MyVector (size_t size, T initialValue); void push_back (T element) ; void pop_back () ; void insert (size_t index,T element) ; T& at (int index) ; size_t size () ; size_t capacity () ; void erase (size_t index) ; void clear () ; bool empty () const ; bool isFull () const ; void resizeArray () ; private : T* arrayHead; size_t arraySize = 0 ; size_t arrayCapacity = 0 ; }; #endif
MyVector.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 #include "MyVector.h" #include <stdexcept> template <typename T>MyVector::MyVector () { this ->arrayHead = new T[5 ]; this ->arrayCapacity = 5 ; this ->arraySize = 0 ; } MyVector::MyVector (size_t size){ this ->arrayHead = new T[size]; this ->arraySize = 0 ; this ->arrayCapacity = size; } MyVector::MyVector (size_t size, T initialValue){ this ->arrayHead = new T[size]; this ->arraySize = size; this ->arrayCapacity = size; for (size_t i = 0 ; i < arraySize ; i++){ arrayHead[i] = initialValue; } } void MyVector::push_back (T element) { if (this ->isFull ()){ this ->resizeArray (); } this ->arrayHead[this ->arraySize] = element; this ->arraySize ++; } void MyVector::pop_back () { this ->arraySize --; } void MyVector::insert (size_t index,T element) { if (index >= arraySize){ throw invalid_argument ("Outbound insertion! " ); } if (this ->isFull ()){ this ->resizeArray (); } if (index == this ->arraySize){ this ->arrayHead[index] = element; this ->arraySize++; }else { T* tempHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < index; i++){ tempHead[i] = (this ->arrayHead)[i]; } tempHead[index] = element; for (size_t i= index + 1 ; i< (this ->arraySize+1 ); i++){ tempHead[i] = (this ->arrayHead)[i]; } delete this ->arrayHead; this ->arrayHead = tempHead; this ->arraySize++; } } T& MyVector::at (int index) { if (index >= (this ->arraySize)){ throw invalid_argument ("Outbound Vector" ); } return this ->arrayHead[index]; } size_t MyVector::size () { return this ->arraySize; } size_t MyVector::capacity () { return this ->arrayCapacity; } void MyVector::clear () { delete this ->arrayHead; this ->arrayHead = nullptr ; this ->arraySize = 0 ; this ->arrayCapacity = 0 ; } bool MyVector::empty () const { if ((this ->arraySize) == 0 ){ return true ; }else { return false ; } } bool MyVector::isFull () const { if (this ->arraySize == this ->arrayCapacity){ return true ; }else { return false ; } } void MyVector::resizeArray () { T* tempHead = new T[2 *(this ->arrayCapacity)]; for (size_t i =0 ;i < this ->arrayCapacity;i++){ tempHead[i] = (this ->arrayHead)[i]; } delete this ->arrayHead; this ->arrayHead = tempHead; this ->arraySize = arrayCapacity; this ->arrayCapacity = 2 *(this ->arrayCapacity); } void MyVector::erase (size_t index) { if (index >= this ->arraySize){ throw invalid_argument ("Outbound vector" ); } if (index == ((this ->arraySize)-1 )){ this ->arraySize = this ->arraySize -1 ; }else { T* tempHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < index; i++){ tempHead[i] = (this ->arrayHead)[i]; } for (size_t i= index; i< (this ->arraySize-1 ); i++){ tempHead[i] = (this ->arrayHead)[i+1 ]; } delete this ->arrayHead; this ->arrayHead = tempHead; this ->arraySize = this ->arraySize -1 ; } }
Template Class Member Functions Must Be Defined as Templates Error: I defined a template class , but the member function definitions outside the class (in the .cpp file) are missing the template<typename T> prefix and the MyVector<T>:: scope qualifier.
Lesson:
For every member function of a template class, I must prefix the definition with:
1 2 3 4 template <typename T>MyVector<T>::functionName () { }
This applies to all member functions.
Correct Way: 1 2 3 4 template <typename T>MyVector<T>::MyVector () { }
Incorrect Use of delete for Arrays Error: I wrote:
But this is used for deleting a single object , not an array.
Lesson:
When deleting a dynamically allocated array, I must use delete[] , not delete.
Correct Way: 1 delete [] this ->arrayHead;
resizeArray Should Not Modify arraySizeError: 1 this ->arraySize = arrayCapacity;
This changes the logical size of the array during a resize, which is incorrect.
Lesson:
resizeArray only increases capacity , not the actual number of stored elements (arraySize).
Never modify arraySize in a resize function.
Logic Error in insert Function Error: 1 if (index == this ->arraySize)
This block never runs, because earlier I have:
which already excludes the == case.
Lesson:
I must restructure the conditional logic to properly handle inserting at the end.
pop_back Does Not Check for Empty ArrayError: In pop_back, I decrease arraySize unconditionally.
Lesson:
If arraySize is already 0, decrementing it will wrap around (since it’s unsigned), causing undefined behaviour .
Fix: Add a safety check:
1 2 3 if (this ->arraySize > 0 ) { this ->arraySize--; }
at Function Should Also Check for Negative IndexesError: my at function checks:
But doesn’t handle negative indices (index < 0), which can happen if index is an int.
Lesson:
While standard std::vector::at() doesn’t always check negatives (depends on overload), for safety, I should.
Suggested Check: 1 2 3 if (index < 0 || index >= arraySize) { throw invalid_argument ("Invalid Index" ); }
Use size_t Type Parameter in at Function Error: my at function checks:
1 2 3 4 5 6 T& MyVector<T>::at (index index){ if (index >= (this ->arraySize)){ throw invalid_argument ("Outbound Vector" ); } return this ->arrayHead[index]; }
But it may not work when comparing int type index with an size_t type arraySize.
Lesson:
Always use size_t when it refers to a large number in C++
Fix 1 2 3 4 5 6 7 8 9 10 template <typename T>T& MyVector<T>::at (size_t index){ if (index >= (this ->arraySize)){ throw invalid_argument ("Outbound Vector" ); } if (index < 0 ){ throw invalid_argument ("Invalid Index" ); } return this ->arrayHead[index]; }
Error: In MyVector.h, I have:
Lesson:
This is bad practice in headers—it imports std into all files that include my header.
This pollutes the global namespace and may cause name conflicts .
Fix:
Remove using namespace std; from headers.
Use std:: explicitly (e.g., std::size_t, std::string).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 template <typename T>MyVector<T>::MyVector (const MyVector<T>& other) { this ->arraySize = other.arraySize; this ->arrayCapacity = other.arrayCapacity; this ->arrayHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < this ->arraySize; ++i) { this ->arrayHead[i] = other.arrayHead[i]; } } template <typename T>MyVector<T>& MyVector<T>::operator =(const MyVector<T>& other) { if (this == &other) return *this ; delete [] this ->arrayHead; this ->arraySize = other.arraySize; this ->arrayCapacity = other.arrayCapacity; this ->arrayHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < this ->arraySize; ++i) { this ->arrayHead[i] = other.arrayHead[i]; } return *this ; }
Robust Resize Logic: Avoid Zero-Capacity Allocations Problem: If arrayCapacity is zero when resizeArray() is called (e.g., after clear()), I will attempt to allocate a zero-length array.
Fix: Add a condition:
1 2 3 if (this ->arrayCapacity == 0 ) { this ->arrayCapacity = 1 ; }
Then proceed to double it normally.
Avoid Magic Number in Default Constructor Problem: 1 2 3 4 5 MyVector<T>::MyVector (){ this ->arrayHead = new T[5 ]; this ->arrayCapacity = 5 ; this ->arraySize = 0 ; }
5 is a magic number in my code
I will use 5 twice because in resize function, if it is an empty vector after clear operation
Fix:
Use hash define to avoid magic number
Missing Copy Constructor and Assignment Operator Problem: my class manages dynamic memory (arrayHead) , so it needs:
A copy constructor
A copy assignment operator
Lesson:
Without them, the compiler generates shallow copies , which leads to double deletions or dangling pointers .
Fix: Implement the Rule of Three:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 template <typename T>MyVector<T>::MyVector (const MyVector<T>& other) { this ->arraySize = other.arraySize; this ->arrayCapacity = other.arrayCapacity; this ->arrayHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < this ->arraySize; ++i) { this ->arrayHead[i] = other.arrayHead[i]; } } template <typename T>MyVector<T>& MyVector<T>::operator =(const MyVector<T>& other) { if (this == &other) return *this ; delete [] this ->arrayHead; this ->arraySize = other.arraySize; this ->arrayCapacity = other.arrayCapacity; this ->arrayHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < this ->arraySize; ++i) { this ->arrayHead[i] = other.arrayHead[i]; } return *this ; }
Final Corrected Code MyVector.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 #include "MyVector.h" #include <cstddef> #include <stdexcept> #define VEC_MIN_CAP 5 template <typename T>MyVector<T>::MyVector (){ this ->arrayHead = new T[VEC_MIN_CAP]; this ->arrayCapacity = VEC_MIN_CAP; this ->arraySize = 0 ; } template <typename T>MyVector<T>::MyVector (size_t size){ this ->arrayHead = new T[size]; this ->arraySize = 0 ; this ->arrayCapacity = size; } template <typename T>MyVector<T>::MyVector (size_t size, T initialValue){ this ->arrayHead = new T[size]; this ->arraySize = size; this ->arrayCapacity = size; for (size_t i = 0 ; i < arraySize ; i++){ arrayHead[i] = initialValue; } } template <typename T>MyVector<T>::MyVector (const MyVector<T>& other) { this ->arraySize = other.arraySize; this ->arrayCapacity = other.arrayCapacity; this ->arrayHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < this ->arraySize; ++i) { this ->arrayHead[i] = other.arrayHead[i]; } } template <typename T>void MyVector<T>::push_back (T element){ if (this ->isFull ()){ this ->resizeArray (); } this ->arrayHead[this ->arraySize] = element; this ->arraySize ++; } template <typename T>void MyVector<T>::pop_back (){ if (this ->arraySize > 0 ) { this ->arraySize--; }else { throw invalid_argument ("Empty Vector! Nothing available to pop back" ); } } template <typename T>void MyVector<T>::insert (size_t index,T element){ if (index > arraySize){ throw invalid_argument ("Outbound insertion! " ); } if (this ->isFull ()){ this ->resizeArray (); } if (index == this ->arraySize){ this ->arrayHead[index] = element; this ->arraySize++; }else { T* tempHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < index; i++){ tempHead[i] = (this ->arrayHead)[i]; } tempHead[index] = element; for (size_t i= index + 1 ; i< (this ->arraySize+1 ); i++){ tempHead[i] = (this ->arrayHead)[i]; } delete [] this ->arrayHead; this ->arrayHead = tempHead; this ->arraySize++; } } template <typename T>T& MyVector<T>::at (size_t index){ if (index >= (this ->arraySize)){ throw invalid_argument ("Outbound Vector" ); } if (index < 0 ){ throw invalid_argument ("Invalid Index" ); } return this ->arrayHead[index]; } template <typename T>size_t MyVector<T>::size (){ return this ->arraySize; } template <typename T>size_t MyVector<T>::capacity (){ return this ->arrayCapacity; } template <typename T>void MyVector<T>::clear (){ delete [] this ->arrayHead; this ->arrayHead = nullptr ; this ->arraySize = 0 ; this ->arrayCapacity = 0 ; } template <typename T>bool MyVector<T>::empty () const { if ((this ->arraySize) == 0 ){ return true ; }else { return false ; } } template <typename T>bool MyVector<T>::isFull () const { if (this ->arraySize == this ->arrayCapacity){ return true ; }else { return false ; } } template <typename T>void MyVector<T>::resizeArray (){ if (this ->arrayCapacity == 0 ){ this ->arrayHead = new T[VEC_MIN_CAP]; this ->arrayCapacity = VEC_MIN_CAP; this ->arraySize = 0 ; } T* tempHead = new T[2 *(this ->arrayCapacity)]; for (size_t i =0 ;i < this ->arraySize;i++){ tempHead[i] = (this ->arrayHead)[i]; } delete [] this ->arrayHead; this ->arrayHead = tempHead; this ->arrayCapacity = 2 *(this ->arrayCapacity); } template <typename T>void MyVector<T>::erase (size_t index){ if (index >= this ->arraySize){ throw invalid_argument ("Outbound vector" ); } if (index == ((this ->arraySize)-1 )){ this ->arraySize = this ->arraySize -1 ; }else { T* tempHead = new T[this ->arrayCapacity]; for (size_t i = 0 ; i < index; i++){ tempHead[i] = (this ->arrayHead)[i]; } for (size_t i= index; i< (this ->arraySize-1 ); i++){ tempHead[i] = (this ->arrayHead)[i+1 ]; } delete [] this ->arrayHead; this ->arrayHead = tempHead; this ->arraySize = this ->arraySize -1 ; } }
Key Takeaways for Future Code:
Mistake
Lesson
Template functions defined without template<typename T> and MyVector<T>::
Always define template member functions with the proper syntax.
Used delete for arrays
Use delete[] to deallocate dynamic arrays safely.
resizeArray() modified arraySize
Only update capacity, not logical size.
insert() logic blocked end-insertion
Adjust condition order to handle insertion at the end correctly.
pop_back() lacked empty check
Add a guard: don’t decrement arraySize if it’s already 0.
at() didn’t check for negative index
Validate both index < 0 and index >= arraySize to prevent UB.
at() used int index
Use size_t for consistency and type safety with container sizes.
Used using namespace std; in header
Avoid this—use std:: explicitly to prevent namespace pollution.
Missing Rule of Three
Implement copy constructor and assignment operator for deep copying.
resizeArray() risked zero-capacity allocation
Ensure minimum capacity before doubling; avoid zero-sized allocation.
Magic number in constructor (5)
Use a #define or constexpr to name constants clearly.
erase() and insert() did manual shifting with fresh array
Try shifting in-place or reuse memory instead of reallocating every time.
Template Syntax: Template functions must repeat the template<typename T> prefix and use MyVector<T>:: scope qualifier.
Memory Management: Always pair new[] with delete[], and ensure the Rule of Three (or Rule of Five in modern C++) is followed.
Safety First: Always check boundary conditions like size or null pointers before modifying memory.
Clarity: Avoid magic numbers, use descriptive constants, and prefer expressive error messages.
Design: Separate concerns: resizing should never affect logical size. Avoid polluting headers or redoing work unnecessarily.