Vector Container Definition
In C++, a vector is a sequence container that encapsulates dynamic arrays. Its usage typically follows this pattern:
template<typename T>
class MyVector;
// Or in practice
std::vector<int> numbers;
Here, T represents the template parameter, which can be any built-in or user-defined type.
Initialization and Assignment Methods
Brace Initialization C++ supports brace initialization inherited from C:
std::vector<int> values{10, 20, 30, 40, 50};
This constructs an integer vector with six elements initialized to the specified values.
Parentheses Initialization C++ also provides constructor-style initialization:
std::vector<double> vec1(8); // 8 elements with value 0.0
std::vector<char> vec2(5, 'A'); // 5 elements with value 'A'
These correspond to two different constructor overloads.
Element Assignment Individual elements can be assigned using subscript notation:
std::vector<float> data(7);
data[0] = 3.14f;
for(size_t idx = 0; idx < data.size(); ++idx) {
data[idx] = idx * 1.5f;
}
Iterators in Vector
Iterators provide pointer-like access to container elements. They enable various operations including container initialization:
Iterator-Based Initialization
std::vector<int> source(15);
std::vector<int> destination(source.begin(), source.end());
This creates destination by copying all elements from source.
Implementing a Custom Vector Class
Basic Class Structure
template<typename ElementType>
class MyVector {
public:
using iterator = ElementType*;
using const_iterator = const ElementType*;
MyVector() : data_start(nullptr), data_end(nullptr), storage_end(nullptr) {}
private:
iterator data_start;
iterator data_end;
iterator storage_end;
};
Fundamental Member Functions
size_t capacity() const {
return storage_end - data_start;
}
size_t size() const {
return data_end - data_start;
}
ElementType& operator[](size_t index) {
assert(index < size());
return data_start[index];
}
iterator begin() { return data_start; }
iterator end() { return data_end; }
void swap(MyVector<ElementType>& other) {
std::swap(data_start, other.data_start);
std::swap(data_end, other.data_end);
std::swap(storage_end, other.storage_end);
}
~MyVector() {
if(data_start) {
delete[] data_start;
data_start = data_end = storage_end = nullptr;
}
}
Memory Management Funcsions
reserve() Implementation:
void reserve(size_t requested_capacity) {
if(requested_capacity > capacity()) {
size_t current_size = size();
ElementType* new_buffer = new ElementType[requested_capacity];
if(data_start) {
for(size_t i = 0; i < current_size; ++i) {
new_buffer[i] = data_start[i];
}
delete[] data_start;
}
data_start = new_buffer;
data_end = data_start + current_size;
storage_end = data_start + requested_capacity;
}
}
Element Operations
push_back():
void push_back(const ElementType& element) {
if(data_end == storage_end) {
size_t new_cap = capacity() == 0 ? 8 : capacity() * 2;
reserve(new_cap);
}
*data_end = element;
++data_end;
}
pop_back():
void pop_back() {
assert(size() > 0);
--data_end;
}
insert():
void insert(iterator position, const ElementType& element) {
assert(position >= data_start && position <= data_end);
if(data_end == storage_end) {
size_t offset = position - data_start;
size_t new_cap = capacity() == 0 ? 8 : capacity() * 2;
reserve(new_cap);
position = data_start + offset;
}
iterator current = data_end;
while(current > position) {
*current = *(current - 1);
--current;
}
*position = element;
++data_end;
}
resize():
void resize(size_t new_size, ElementType value = ElementType()) {
if(new_size > size()) {
reserve(new_size);
while(data_end < data_start + new_size) {
*data_end = value;
++data_end;
}
} else {
data_end = data_start + new_size;
}
}
erase():
void erase(iterator position) {
assert(position >= data_start && position < data_end);
iterator next = position + 1;
while(next < data_end) {
*(next - 1) = *next;
++next;
}
--data_end;
}
Iterator Invalidation Concepts
Iterator invalidation occurs primarily during reallocation operations. When a vector expands its capacity, existing iterators may point to deallocated memory. Both insert() and erase() operations in the standard library return updated iterators to address this issue.
Deep Copy Considerations
Using memcpy or memmove with non-trivial types can cause shallow copy problems. The corrected reserve() function uses element-wise assignment:
void reserve(size_t requested_capacity) {
if(requested_capacity > capacity()) {
size_t current_size = size();
ElementType* new_buffer = new ElementType[requested_capacity];
if(data_start) {
for(size_t i = 0; i < current_size; ++i) {
new_buffer[i] = data_start[i]; // Proper copy assignment
}
delete[] data_start;
}
data_start = new_buffer;
data_end = data_start + current_size;
storage_end = data_start + requested_capacity;
}
}
Constructors Implementation
// Copy constructor
MyVector(const MyVector<ElementType>& other) {
reserve(other.capacity());
for(const auto& item : other) {
push_back(item);
}
}
// Iterator range constructor
template<typename InputIter>
MyVector(InputIter first, InputIter last) {
while(first != last) {
push_back(*first);
++first;
}
}
// Fill constructor
MyVector(size_t count, const ElementType& value = ElementType()) {
resize(count, value);
}
Note on Built-in Types: C++ treats built-in types as having default constructors (int() yields 0, double() yields 0.0, char() yields '\0', std::string() yields empty string).