Following on from my two previous posts.
I have written a detailed blog about how to write a minimal vector like class. This set of articles has been inspired by multiple posts here on http://codereview.stackexchange.com (See Sources).
The final result is below.
But now it is my turn for some review to make sure I did not screw up too much. :-)
Head:
#include <type_traits>
#include <memory>
#include <algorithm>
#include <stdexcept>
#include <iterator>
Types:
template<typename T>
class Vector
{
public:
using value_type = T;
using reference = T&;
using const_reference = T const&;
using pointer = T*;
using const_pointer = T const*;
using iterator = T*;
using const_iterator = T const*;
using difference_type = std::ptrdiff_t;
using size_type = std::size_t;
private:
size_type capacity;
size_type length;
T* buffer;
struct Deleter
{
void operator()(T* buffer) const
{
::operator delete(buffer);
}
};
Constructors:
public:
Vector(int capacity = 10)
: capacity(capacity)
, length(0)
, buffer(static_cast<T*>(::operator new(sizeof(T) * capacity)))
{}
template<typename I>
Vector(I begin, I end)
: capacity(std::distance(begin, end))
, length(0)
, buffer(static_cast<T*>(::operator new(sizeof(T) * capacity)))
{
for(auto loop = begin;loop != end; ++loop)
{
pushBackInternal(*loop);
}
}
Vector(std::initializer_list<T> const& list)
: Vector(std::begin(list), std::end(list))
{}
~Vector()
{
// Make sure the buffer is deleted even with exceptions
// This will be called to release the pointer at the end
// of scope.
std::unique_ptr<T, Deleter> deleter(buffer, Deleter());
clearElements<T>();
}
Vector(Vector const& copy)
: capacity(copy.length)
, length(0)
, buffer(static_cast<T*>(::operator new(sizeof(T) * capacity)))
{
try
{
for(int loop = 0; loop < copy.length; ++loop)
{
push_back(copy.buffer[loop]);
}
}
catch(...)
{
clearElements<T>();
::operator delete(buffer);
// Make sure the exceptions continue propagating after
// the cleanup has completed.
throw;
}
}
Vector& operator=(Vector const& copy)
{
copyAssign<T>(copy);
return *this;
}
Vector(Vector&& move) noexcept
: capacity(0)
, length(0)
, buffer(nullptr)
{
move.swap(*this);
}
Vector& operator=(Vector&& move) noexcept
{
move.swap(*this);
return *this;
}
void swap(Vector& other) noexcept
{
using std::swap;
swap(capacity, other.capacity);
swap(length, other.length);
swap(buffer, other.buffer);
}
Access:
reference operator[](size_type index) {return buffer[index];}
const_reference operator[](size_type index) const {return buffer[index];}
reference at(size_type index) {validateIndex(index);return buffer[index];}
const_reference at(size_type index) const {validateIndex(index);return buffer[index];}
reference front() {return buffer[0];}
const_reference front() const {return buffer[0];}
reference back() {return buffer[length - 1];}
const_reference back() const {return buffer[length - 1];}
Comparison:
bool operator!=(Vector const& rhs) const {return !(*this == rhs);}
bool operator==(Vector const& rhs) const
{
return (size() == rhs.size())
? std::equal(begin(), end(), rhs.begin())
: false;
}
Iterators:
iterator begin() {return buffer;}
iterator rbegin() {return std::reverse_iterator<iterator>(end());}
const_iterator begin() const {return buffer;}
const_iterator rbegin() const {return std::reverse_iterator<iterator>(end());}
iterator end() {return buffer + length;}
iterator rend() {return std::reverse_iterator<iterator>(begin());}
const_iterator end() const {return buffer + length;}
const_iterator rend() const {return std::reverse_iterator<iterator>(begin());}
const_iterator cbegin() const {return begin();}
const_iterator crbegin() const {return rbegin();}
const_iterator cend() const {return end();}
const_iterator crend() const {return rend();}
Non-Mutating Functions:
size_type size() const {return length;}
bool empty() const {return length == 0;}
Mutating Functions:
void push_back(T const& value)
{
resizeIfRequire();
pushBackInternal(value);
}
void push_back(T&& value)
{
resizeIfRequire();
moveBackInternal(std::forward<T>(value));
}
template<typename... Args>
void emplace_back(Args&&... args)
{
resizeIfRequire();
constructBackInternal(std::forward<T>(args)...);
}
void pop_back()
{
--length;
buffer[length].~T();
}
void reserve(size_type capacityUpperBound)
{
if (capacityUpperBound > capacity)
{
reserveCapacity(capacityUpperBound);
}
}
Private:
private:
void validateIndex(size_type index)
{
if (index >= length)
{
throw std::out_of_range("Out of Range");
}
}
void resizeIfRequire()
{
if (length == capacity)
{
size_type newCapacity = capacity * 1.62;
reserveCapacity(newCapacity);
}
}
void reserveCapacity(size_type newCapacity)
{
Vector<T> tmpBuffer(newCapacity);
simpleCopy<T>(tmpBuffer);
tmpBuffer.swap(*this);
}
void pushBackInternal(T const& value)
{
new (buffer + length) T(value);
++length;
}
void moveBackInternal(T&& value)
{
new (buffer + length) T(std::forward<T>(value));
++length;
}
template<typename... Args>
void constructBackInternal(Args&&... args)
{
new (buffer + length) T(std::forward<Args>(args)...);
++length;
}
template<typename X>
typename std::enable_if<std::is_nothrow_move_constructible<X>::value == false>::type
simpleCopy(Vector<T>& dst)
{
std::for_each(buffer, buffer + length,
[&dst](T const& v){dst.pushBackInternal(v);}
);
}
template<typename X>
typename std::enable_if<std::is_nothrow_move_constructible<X>::value == true>::type
simpleCopy(Vector<T>& dst)
{
std::for_each(buffer, buffer + length,
[&dst](T& v){dst.moveBackInternal(std::move(v));}
);
}
template<typename X>
typename std::enable_if<std::is_trivially_destructible<X>::value == false>::type
clearElements()
{
// Call the destructor on all the members in reverse order
for(int loop = 0; loop < length; ++loop)
{
// Note we destroy the elements in reverse order.
buffer[length - 1 - loop].~T();
}
}
template<typename X>
typename std::enable_if<std::is_trivially_destructible<X>::value == true>::type
clearElements()
{
// Trivially destructible objects can be re-used without using the destructor.
}
template<typename X>
typename std::enable_if<(std::is_nothrow_copy_constructible<X>::value
&& std::is_nothrow_destructible<X>::value) == true>::type
copyAssign(Vector<X>& copy)
{
if (this == ©)
{
return;
}
if (capacity <= copy.length)
{
clearElements<T>();
length = 0;
for(int loop = 0; loop < copy.length; ++loop)
{
pushBackInternal(copy[loop]);
}
}
else
{
// Copy and Swap idiom
Vector<T> tmp(copy);
tmp.swap(*this);
}
}
template<typename X>
typename std::enable_if<(std::is_nothrow_copy_constructible<X>::value
&& std::is_nothrow_destructible<X>::value) == false>::type
copyAssign(Vector<X>& copy)
{
// Copy and Swap idiom
Vector<T> tmp(copy);
tmp.swap(*this);
}
};