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:
#ifndef THORSANVIL_CONTAINER_VECTOR
#define THORSANVIL_CONTAINER_VECTOR
#include <type_traits>
#include <memory>
#include <algorithm>
#include <stdexcept>
#include <iterator>
#include <cmath>
namespace ThorsAnvil
{
namespace Container
{
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 = std::max(2.0, 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);
}
};
Tail:
}
}
#endif
rbegin()
function needs to returnreverse_iterator
, notiterator
. YourmoveBackInternal
function needs to callstd::move(value)
, notstd::forward<T>(value)
. I suspect thatVector<int>(1).push_back(2)
will corrupt memory. \$\endgroup\$rbegin()/rend()
oops mistake added unit tests for that. Now fixed.std::forward Vs std::move
still working on move semantics; so I get that wrong sometimes. Will have to write an article about the subject. I doubt that there is memory corruption with that. But while adding some unit tests to test the theory; I did find another error. Needstd::max()
to make sure capacity of 0/1 scales up correctly. \$\endgroup\$#include <vector>
. \$\endgroup\$std::vector
is written the way it is. This is the result of five blog posts (so you can see the reason for each feature in the blog). But even my code needs a review. :-) \$\endgroup\$