Please give me some feedback on my attempt at a vector. I wanted this to be the lowest level concept of a vector that I'm using, so I'm primarily concerned with speed, and I would build wrappers afterward for safety when its needed. This is why I'm not using the copy and swap idiom for the copy ctor. Anyway please critique and give explanations for changes where it can be improved for my goal.
vector.h:
#ifndef VECTOR_H
#define VECTOR_H
template<typename T>
class Vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
Vector();
Vector(unsigned int size);
Vector(unsigned int size, const T& initial);
Vector(const Vector<T>& v);
Vector(Vector<T> && v);
~Vector();
Vector<T>& operator =(Vector<T> const& v);
Vector<T>& operator =(Vector<T> && v); // move assignment
inline unsigned int capacity() const { return m_capacity; }
inline unsigned int size() const { return m_size; }
inline bool empty() const { return m_size == 0; }
inline iterator begin() { return buff; }
inline iterator end() { return buff + (m_size - 1) * sizeof(T); }
inline const_iterator c_begin() { return buff; }
inline const_iterator c_end() { return buff + (m_size - 1) * sizeof(T); }
T& front();
T& back();
void pushBack(const T& val);
void popBack();
void reserve(unsigned int capacity);
void resize(unsigned int size);
T& operator [](unsigned int index);
void clear();
private:
unsigned int m_capacity;
unsigned int m_size;
unsigned int Log;
T* buff;
void swap(Vector<T> & v);
};
#endif
vector.cpp:
#include "vector.h"
#include <cmath> //for Log
template<typename T>
Vector<T>::Vector() :
m_capacity(0),
m_size(0),
Log(0),
buff(nullptr) {}
template<typename T>
Vector<T>::Vector(unsigned int size) : //an argument of 0 leads to a capacity value of 1, distinct from the default ctor
m_size(size),
Log(ceil(log((double)size) / log(2.0))),
m_capacity(1 << Log),
buff(size ? new T[m_capacity] : nullptr) {}
template<typename T>
Vector<T>::Vector(unsigned int size, const T& initial) :
m_size(size),
Log(ceil(log((double)size) / log(2.0))),
m_capacity(1 << Log),
buff(size ? new T[m_capacity] : nullptr)
{
for (unsigned int i = 0; i < size; i++)
{
//using placement new to place each element of v[i] in our already allocated buffer
new(buffer + i) T(initial);
}
}
template<typename T>
Vector<T>::Vector(Vector<T> const& v) :
m_capacity(v.capacity()),
m_size(v.size()),
Log(v.Log),
buff(m_size ? new T[m_capacity] : nullptr)
{
std::copy(v.buff, v.buff + v.m_size, buff); //std::copy is better than loops if we have both arrays
//for (unsigned int i = 0; i < m_size; i++)
//new(buffer + sizeof(T)*i) T(v[i]);
}
template<typename T>
Vector<T>::Vector(Vector<T> && v)
: m_size(0),
m_capacity(0),
buff(nullptr),
Log(0)
{
swap(v);
}
template<typename T>
Vector<T>& Vector<T>::operator =(Vector<T> const& v)
// //not strong exception, but will write a wrapper that makes it strong exception; lowest level -> fastest
{
delete[] buff;
buff = nullptr;
buff = v.m_capacity ? new T[v.m_capacity * sizeof(T)] : nullptr;
m_size = v.m_size;
m_capacity = v.m_capacity;
Log = v.Log;
std::copy(v.buff, v.buff + m_size-1, buff);
return *this;
}
template<typename T>
Vector<T>& Vector<T>::operator =(Vector<T> && v)
{
delete[] buff; //prep this
m_size = 0;
buff = nullptr;
m_capacity = 0;
Log = 0;
swap(v);
return *this;
}
template<typename T>
Vector<T>::~Vector()
{
delete[] buff;
buff = nullptr;
m_size = 0;
m_capacity = 0;
Log = 0;
}
template<typename T>
T& Vector<T>::operator[](unsigned int index)
{
return buff[index];
}
template<typename T>
T& Vector<T>::front()
{
return buff[0];
}
template<typename T>
T& Vector<T>::back()
{
return buff[m_size - 1];
}
template<typename T>
void Vector<T>::reserve(unsigned int capac)
{
T* newbuff = new T[capac];
for (unsigned int i = 0; i < m_size; i++)
newbuff[i] = buff[i];
m_capacity = capac;
delete[] buff;
buff = newbuff;
}
template<typename T>
void Vector<T>::resize(unsigned int size)
{
Log = ceil(log((double)size) / log(2.0));
reserve(1 << Log);
m_size = size;
}
template<typename T>
void Vector<T>::pushBack(const T& val)
{
if (m_size >= m_capacity)
{
reserve(1 << Log++);
}
buff[m_size++] = val;
}
template<typename T>
void Vector<T>::popBack()
{
(reinterpret_cast<T*>(buff)[m_size-- - 1]).~T();
}
template<typename T>
void Vector<T>::clear()
{
for (int i = 0; i < m_size; i++)
(reinterpret_cast<T*>(buff)[i]).~T();
m_size = 0;
m_capacity = 0;
Log = 0;
buff = 0;
}
template<typename T>
void Vector<T>::swap(Vector<T> & v)
{
std::swap(m_size, v.m_size);
std::swap(m_capacity, v.m_capacity);
std::swap(buff, v.buff);
std::swap(Log, v.Log);
}
std::vector
? \$\endgroup\$push_back
andcbegin
). \$\endgroup\$inline
as a keyword for functions defined in class is useless because these functions getinline
automatic. \$\endgroup\$