For practice purposes, I reimplemented a STL Vector container. This container has most/all of the capabilities of STL vector.In my previous post, the reviewers made critical and concised observations which I researched about and considered them very important, thanks to them, I made a more robust class than the previous iteration.
Overview
Class Vector is a STL container ( first class container ) that supports various functionalities such as push_back(), pop_back(), front(), back(), at() and many more.
Purpose/Aim
- As an exercise to get in-depth knowledge on the rule of 0/3/5
- I aim to understand more about move semantics
- To get my hands dirty with templates early on
Major Concerns
- Comparisons between iterators
begin()
andcbegin()
returns true, I cross-checked with the standard implementation and same case. Since am trying to mimic the standard, I had to implement it that way. Does it make sense to implement otherwise?
General advice, major/common pitfalls, general good practices and constructive critic are highly welcomed
#ifndef VECTOR_H_
#define VECTOR_H_
#include <cstddef>
#include <algorithm>
#include <iostream>
#include <initializer_list>
namespace MyIterator {
template< typename T >
class Iterator
{
protected:
std::size_t index_;
T* memory_ptr;
public:
Iterator( T* memory_block ) : index_( 0 ), memory_ptr( memory_block ) {}
Iterator( T* memory_block, std::size_t end ) : index_( end ), memory_ptr( memory_block ) {}
Iterator &operator++() {
++index_;
return *this;
}
Iterator &operator--() {
--index_;
return *this;
}
bool operator==( const Iterator &other_it ) const { return ( index_ == other_it.index_ ); }
bool operator!=( const Iterator &other_it ) const { return !( *this == other_it ); }
T& operator*() { return memory_ptr[ index_ ]; }
};
template< typename T >
class ConstIterator : public Iterator<T>
{
public:
ConstIterator( T* memory_block ) : Iterator<T>( memory_block ){}
ConstIterator( T* memory_block, std::size_t end ) : Iterator<T>( memory_block, end ){}
const T& operator*() const { return this->memory_ptr[ this->index_ ]; }
};
}
namespace MyVector {
template< typename T >
class Vector
{
friend void swap( Vector &lhs, Vector &rhs )
{
using std::swap;
swap( lhs.size_index, rhs.size_index );
swap( lhs.vector_capacity, rhs.vector_capacity );
swap( lhs.memory_block, rhs.memory_block );
}
private:
std::size_t size_index;
std::size_t vector_capacity;
T* memory_block;
public:
/* ctors */
Vector() : size_index( 0 ), vector_capacity( 1 ), memory_block ( new T[ vector_capacity ]{} ){}
Vector( std::size_t sz ) : size_index( sz ), vector_capacity( sz ), memory_block( new T[ vector_capacity ]{} ) {}
Vector( std::size_t sz, const T &val ) : Vector( sz )
{
for( std::size_t i = 0; i != vector_capacity; ++i ) {
memory_block[ i ] = val;
++size_index;
}
}
Vector( std::initializer_list<T> list ) : Vector( list.size() )
{
std::size_t index = 0;
for( const auto &item : list )
memory_block[ index++ ] = item;
}
Vector( const Vector &other_vec )
: size_index( other_vec.size_index),
vector_capacity( other_vec.vector_capacity ),
memory_block( new T[ other_vec.vector_capacity ] )
{
std::copy( other_vec.memory_block, other_vec.memory_block + vector_capacity, memory_block );
}
Vector &operator=( Vector rhs )
{
swap( *this, rhs );
return *this;
}
Vector( Vector &&other_vec ) { swap( *this, other_vec ); }
/* modifiers */
void push_back( const T &val )
{
if( size_index >= vector_capacity ) {
// allocates twice the size of the current vector's capacity
T * new_memory_block = new T[ vector_capacity * 2 ];
// copy each element to new vector's memory_block
for( std::size_t i = 0; i != size_index; ++i )
new_memory_block[ i ] = memory_block[ i ];
delete [] memory_block; // delete old memory; very important!
memory_block = new_memory_block;
vector_capacity *= 2;
}
memory_block[ size_index++ ] = val;
}
void insert( int index, const T& val )
{
if( index < 0 || index > size_index ) throw std::out_of_range( "Exception: index out of range");
for( int temp_index = size_index; temp_index >= index; --temp_index )
{
/* Move elements one step back until we get to the desired location */
memory_block[ temp_index + 1 ] = memory_block[ temp_index ];
}
memory_block[ index ] = val;
size_index++;
}
void erase( int index )
{
for( int temp_index = index; temp_index < size_index; ++temp_index )
{
/* Move elements one step back until we get to the desired location */
memory_block[ temp_index ] = memory_block[ temp_index + 1 ];
}
--size_index;
}
void pop_back() { --size_index; }// ignored garbage value
void clear()
{
delete [] memory_block;
memory_block = nullptr;
size_index = 0;
}
/* accessors */
MyIterator::Iterator<T> begin() { return MyIterator::Iterator<T>( memory_block ); }
MyIterator::Iterator<T> end() { return MyIterator::Iterator<T>( memory_block, size_index ); }
const MyIterator::ConstIterator<T> cbegin() const { return MyIterator::ConstIterator<T>( memory_block ); }
const MyIterator::ConstIterator<T> cend() const { return MyIterator::ConstIterator<T>( memory_block, size_index ); }
T& front() const { return memory_block[ 0 ]; }
T& back() const { return memory_block[ size_index - 1 ]; }
T& front() { return memory_block[ 0 ]; }
T& back() { return memory_block[ size_index - 1 ]; }
T& at( const std::size_t i ) const
{
if( i < 0 || i >= size_index )
throw std::out_of_range( "Exception: index out of range " );
return memory_block[ i ];
}
T& at( const std::size_t i )
{
if( i < 0 || i >= size_index )
throw std::out_of_range( "Exception: index out of range " );
return memory_block[ i ];
}
T& operator[]( const int i ) const { return memory_block[ i ]; }
T& operator[]( const int i ) { return memory_block[ i ]; }
std::size_t capacity() const { return vector_capacity; }
std::size_t size() const { return size_index; }
bool empty() const { return ( size_index == 0 ); }
/* dtor */
~Vector() { delete [] memory_block; }
};
}
#endif
if( 1 < 0
inT& at
seems like a typo. Please fix. \$\endgroup\$