/*
Did some reading and tried to separate the lifetime of the data buffer from the lifetimes of the Ts *in* the buffer.
Ergo: create an uninitialized block of memory and manually construct / destruct the Ts inside of it as needed.
Helpful resources:
https://codereview.stackexchange.com/questions/211241/simple-re-implementation-of-stdvector
https://lokiastari.com/blog/2016/02/27/vector/index.html
https://lokiastari.com/blog/2016/02/29/vector-resource-management-ii-copy-assignment/index.html
*/
#pragma once
#include <algorithm>
#include <memory>
#include <cassert>
#include <stdexcept>
namespace API {
template <typename T>
class Container {
public:
using pointer = T*;
using const_pointer = const T*;
using iterator = T*;
using const_iterator = const T*;
using value_type = T;
using size_type = std::size_t;
using reference = T&;
using const_reference = const T&;
Container() = default;
~Container()
{
clear();
::operator delete(_data);
}
explicit Container(size_type capacity)
: _data(static_cast<pointer>(::operator new(sizeof(value_type) * capacity)))
, _count(0)
, _capacity(capacity)
{
//STL behavior is to fill with defaulted-Ts.
//I'm ignoring that tradition in ordet to delegate to this construction without paying for double-constructions.
}
Container(size_type count, const T& val)
: Container(count)
{
std::uninitialized_fill_n(begin(), count, val); // copy construct each element w/ placement new
_count = count;
}
//range construction
Container(const_iterator begin, const_iterator end)
: Container(static_cast<size_type>(std::distance(begin, end)))
{
assert(begin <= end && "Container(iter, iter): begin & end iterators are reversed");
std::uninitialized_copy(begin, end, begin()); // copy construct each element from range into buffer w/ placement new
_count = static_cast<size_type>(std::distance(begin, end));
}
//copy ctor, delegating to range constructor
explicit Container(const Container& that)
: Container(that.begin(), that.end())
{
}
//list construction, delegating to range constructor
Container(std::initializer_list<T> list)
: Container(std::begin(list), std::end(list))
{
}
//move constructor
Container(Container&& that) noexcept
{
_count = std::exchange(that._count, 0);
_capacity = std::exchange(that._capacity, 0);
_data = std::exchange(that._data, nullptr);
}
//by value assignment idiom. deals with both copy- and move assignment
//also known as: "unifying assignment operator"
//https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap
Container& operator=(Container that) noexcept
{
swap(that);
return *this;
}
void swap(Container& that) noexcept
{
using std::swap;
swap(_data, that._data);
swap(_count, that._count);
swap(_capacity, that._capacity);
}
//ADL overload. See Arthur O'Dwyer: https://youtu.be/7Qgd9B1KuMQ?t=2597
friend void swap(Container<T>& a, Container<T>& b) noexcept
{
a.swap(b);
}
void clear() noexcept
{
//TODO: could be erase(begin(), end()).
while (!empty()) {
pop_back();
}
}
void pop_back() noexcept
{
assert(!empty() && "pop_back() on empty container is undefined!");
back().~value_type();
--_count;
}
void reserve(size_type newCapacity)
{
if (newCapacity <= _capacity) {
return; //never decrease the allocation
}
pointer newBuffer = static_cast<pointer>(::operator new(sizeof(value_type) * newCapacity));
std::uninitialized_move(begin(), end(), newBuffer);
destruct_all(); //run all destructors on the moved-from objects.
::operator delete(_data);
_data = newBuffer;
_capacity = newCapacity;
}
template <typename... Args>
void emplace_back(Args&&... args)
{
resizeIfNeeded();
new (&_data[_count]) value_type(std::forward<Args>(args)...); // construct value in memory of aligned storage using inplace operator new
++_count;
}
void push_back(const T& value)
{
emplace_back(std::forward<const T&>(value));
}
reference at(size_type index)
{
if (index >= size()) {
throw std::out_of_range("Container at() index is out of range!");
}
return _data[index];
}
const_reference at(size_type index) const
{
if (index >= size()) {
throw std::out_of_range("Container at() index is out of range!");
}
return _data[index];
}
reference operator[](size_type index) noexcept
{
assert(index < size() && "Container operator[] index is out of range!");
return _data[index];
}
const_reference operator[](size_type index) const noexcept
{
assert(index < size() && "Container operator[] index is out of range!");
return _data[index];
}
pointer data() noexcept
{
return _data;
}
const_pointer data() const noexcept
{
return _data;
}
reference front() noexcept
{
assert(!empty() && "front() on empty container is UB");
return _data[0];
}
const_reference front() const noexcept
{
assert(!empty() && "front() on empty container is UB");
return _data[0];
}
const_reference back() const noexcept
{
assert(!empty() && "back() on empty container is UB");
return _data[size() - 1];
}
reference back() noexcept
{
assert(!empty() && "back() on empty container is UB");
return _data[size() - 1];
}
bool empty() const noexcept
{
return size() == 0;
}
size_type capacity() const noexcept
{
return _capacity;
}
size_type size() const noexcept
{
return _count;
}
iterator begin() noexcept { return _data; }
const_iterator begin() const noexcept { return _data; }
const_iterator cbegin() const noexcept { return _data; }
const_iterator end() const noexcept { return _data + _count; }
const_iterator cend() const noexcept { return _data + _count; }
iterator end() noexcept { return _data + _count; }
private:
pointer _data = nullptr;
size_type _count = 0;
size_type _capacity = 0;
constexpr static size_type INITIAL_CAPACITY = 2;
constexpr static double GROWTH_FACTOR = 2.0f;
void resizeIfNeeded()
{
if (_capacity == 0) {
reserve(INITIAL_CAPACITY);
} else if (_count == _capacity) {
reserve(static_cast<size_type>(GROWTH_FACTOR * _capacity));
}
}
void destruct_all() noexcept
{
for (reference item : *this) {
item.~value_type();
}
}
};
template <typename T>
bool operator==(const Container<T>& lhs, const Container<T>& rhs) noexcept
{
if (std::size(lhs) != std::size(rhs)) {
return false;
}
return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
}
template <typename T>
bool operator<(const Container<T>& lhs, const Container<T>& rhs) noexcept
{
return std::lexicographical_compare(lhs.begin(), lhs.end(),
rhs.begin(), rhs.end());
}
template <typename T>
bool operator!=(const Container<T>& lhs, const Container<T>& rhs) noexcept
{
return !(lhs == rhs);
}
template <typename T>
bool operator>(const Container<T>& lhs, const Container<T>& rhs) noexcept
{
return rhs < lhs;
}
template <typename T>
bool operator<=(const Container<T>& lhs, const Container<T>& rhs) noexcept
{
!(lhs > rhs);
}
template <typename T>
bool operator>=(const Container<T>& lhs, const Container<T>& rhs) noexcept
{
!(lhs < rhs);
}
}
Comments
0 B
|0 👍
/0 👎