225 lines
6.1 KiB
C++
225 lines
6.1 KiB
C++
#ifndef INCLUDED_TRNX_VECTOR
|
|
#define INCLUDED_TRNX_VECTOR
|
|
|
|
#include <cstddef>
|
|
#include <exception>
|
|
#include <iterator>
|
|
#include <type_traits>
|
|
#include <memory>
|
|
#include <trnx_vector_impl.h>
|
|
|
|
namespace trnx {
|
|
|
|
template <typename T>
|
|
class vector
|
|
{
|
|
public:
|
|
typedef T value_type;
|
|
typedef std::size_t size_type;
|
|
typedef std::ptrdiff_t difference_type;
|
|
typedef T& reference;
|
|
typedef const T& const_reference;
|
|
typedef T* pointer;
|
|
typedef const T* const_pointer;
|
|
|
|
private:
|
|
size_type d_size;
|
|
size_type d_capacity;
|
|
typedef detail::uninitialized<T> buff_type[];
|
|
std::unique_ptr<buff_type> d_buff;
|
|
// `uninitialized<T>` represents enough uninitialized storage for an
|
|
// instance of `T`. The lifetime of the `T` instance is controlled
|
|
// manually via the `.construct` and `.destroy` member functions.
|
|
// An initialized instance can be accessed through `.get`.
|
|
|
|
public:
|
|
typedef detail::iter_impl<detail::uninitialized<T> > iterator; //CHG
|
|
typedef detail::iter_impl<const detail::uninitialized<T> > const_iterator; //CHG
|
|
// `iter_impl` is an iterator type that automatically transforms
|
|
// `uninitialized<T>*` into `T*` when dereferencing.
|
|
|
|
vector() : d_size(0), d_capacity(0), d_buff(nullptr)
|
|
{
|
|
// A default-constructed vector is empty and has no allocated buffer.
|
|
}
|
|
|
|
vector(const vector& rhs)
|
|
: d_size(rhs.d_size),
|
|
d_capacity(rhs.d_capacity),
|
|
|
|
// Allocate enough space for `rhs.d_size` items.
|
|
d_buff(std::make_unique<buff_type>(rhs.d_size))
|
|
{
|
|
// Copy-construct all of `rhs`'s elements into the current buffer.
|
|
for(size_type i = 0; i < d_size; ++i)
|
|
{
|
|
d_buff[i].construct(rhs.d_buff[i].get());
|
|
}
|
|
}
|
|
|
|
~vector()
|
|
{
|
|
// Destroy all elements in the vector. In the Standard, the order of
|
|
// destruction is unspecified.
|
|
for(size_type i = 0; i < d_size; ++i)
|
|
{
|
|
d_buff[i].destroy();
|
|
}
|
|
}
|
|
|
|
vector<T>& operator=(const vector<T>& rhs);
|
|
// The copy-constructor allocates enough memory to store `rhs`'s
|
|
// elements in a new buffer, copies over the current elements to the
|
|
// new buffer, and finally sets `d_buff` to point to the new buffer.
|
|
|
|
size_type size() const { return d_size; }
|
|
size_type capacity() const { return d_capacity; }
|
|
bool empty() const { return d_size == 0; }
|
|
|
|
iterator begin() { return iterator(d_buff.get()); }
|
|
iterator begin() const { return iterator(d_buff.get()); }
|
|
|
|
iterator end() { return iterator(d_buff.get() + d_size); }
|
|
iterator end() const { return iterator(d_buff.get() + d_size); }
|
|
|
|
const_iterator cbegin() const { return const_iterator(d_buff.get()); }
|
|
const_iterator cend() const { return const_iterator(d_buff.get() + d_size); }
|
|
|
|
reference front() { return d_buff[0].get(); }
|
|
const_reference front() const { return d_buff[0].get(); }
|
|
|
|
reference back() { return d_buff[d_size - 1].get(); }
|
|
const_reference back() const { return d_buff[d_size - 1].get(); }
|
|
|
|
reference operator[](size_type i) { return d_buff[i].get(); }
|
|
const_reference operator[](size_type i) const { return d_buff[i].get(); }
|
|
|
|
reference at(size_type i)
|
|
{
|
|
if(i >= d_size)
|
|
{
|
|
throw std::out_of_range("Index out of range");
|
|
}
|
|
else
|
|
{
|
|
return d_buff[i].get();
|
|
}
|
|
}
|
|
|
|
const_reference at(size_type i) const
|
|
{
|
|
// The following use of `const_cast` is legal and simply prevents code
|
|
// repetition with the non-`const` version of `at`.
|
|
return const_cast<vector<T>&>(*this).at(i);
|
|
}
|
|
|
|
void push_back(const T& item);
|
|
void push_back(T&& item);
|
|
void pop_back();
|
|
void clear();
|
|
void reserve(size_type new_capacity);
|
|
};
|
|
|
|
template <typename T>
|
|
void vector<T>::push_back(const T& item)
|
|
{
|
|
if(d_size >= d_capacity)
|
|
{
|
|
const size_type new_capacity = (d_capacity == 0) ? 1
|
|
: d_capacity * 2;
|
|
|
|
reserve(new_capacity);
|
|
}
|
|
|
|
d_buff[d_size++].construct(item);
|
|
}
|
|
|
|
template <typename T>
|
|
void vector<T>::push_back(T&& item)
|
|
{
|
|
if(d_size >= d_capacity)
|
|
{
|
|
const size_type new_capacity = (d_capacity == 0) ? 1
|
|
: d_capacity * 2;
|
|
|
|
reserve(new_capacity);
|
|
}
|
|
|
|
d_buff[d_size++].construct(std::move(item));
|
|
}
|
|
|
|
template <typename T>
|
|
void vector<T>::pop_back()
|
|
{
|
|
BSLS_ASSERT(!empty());
|
|
|
|
d_buff[d_size - 1].destroy();
|
|
--d_size;
|
|
}
|
|
|
|
template <typename T>
|
|
void vector<T>::clear()
|
|
{
|
|
// Destroy all existing elements
|
|
for(size_type i = 0; i < d_size; ++i)
|
|
{
|
|
d_buff[i].destroy();
|
|
}
|
|
|
|
// Set size to zero, but leave capacity unchanged
|
|
d_size = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
vector<T>& vector<T>::operator=(const vector& rhs)
|
|
{
|
|
// Prevent self-assignment
|
|
if(&rhs != this)
|
|
{
|
|
// Destroy all existing elements and set size to zero
|
|
clear();
|
|
|
|
// Reserve if necessary
|
|
reserve(rhs.d_size);
|
|
|
|
// Copy elements from `rhs`
|
|
for(size_type i = 0; i < rhs.d_size; ++i)
|
|
{
|
|
d_buff[i].construct(rhs.d_buff[i].get());
|
|
}
|
|
|
|
// Update size
|
|
d_size = rhs.d_size;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
template <typename T>
|
|
void vector<T>::reserve(size_type new_capacity)
|
|
{
|
|
// Exit early if there's no need to reserve more memory
|
|
if(new_capacity <= d_capacity) { return; }
|
|
|
|
// Allocate a new buffer
|
|
auto buff = std::make_unique<buff_type>(new_capacity);
|
|
|
|
// Copy-construct existing elements into the new buffer and destroy them
|
|
// in the old one
|
|
for(size_type i = 0; i < d_size; ++i)
|
|
{
|
|
buff[i].construct(std::move(d_buff[i].get()));
|
|
d_buff[i].destroy();
|
|
}
|
|
|
|
// Deallocate old buffer, set the owned buffer to `buff`
|
|
d_buff = std::move(buff);
|
|
|
|
// Update capacity
|
|
d_capacity = new_capacity;
|
|
}
|
|
|
|
} // close namespace trnx
|
|
|
|
#endif
|