I have created a heap allocated equivalent of std::array simply because I needed a lightweight fixed-size container that isn't known at compile time. Neither std::array or std::vector offered that, so I made my own. My goal is to make it fully STL compliant.
#pragma once
#include <cstddef>
#include <iterator>
#include <algorithm>
#include <initializer_list>
template<typename T>
class dynamic_array {
public:
// types:
using value_type = T;
using size_type = size_t;
using difference_type = ptrdiff_t;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using iterator = T*;
using const_iterator = const T*;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// construct/copy/move/destroy:
explicit dynamic_array(size_type count) noexcept :
count(count),
buffer(new T[size()]) { }
dynamic_array(const std::initializer_list<T>& list) noexcept :
count(list.size()),
buffer(new T[size()]) {
std::copy(list.begin(), list.end(), begin());
}
dynamic_array(const dynamic_array<T>& other) noexcept :
count(other.size()),
buffer(new T[size()]) {
std::copy(other.begin(), other.end(), begin());
}
dynamic_array(dynamic_array<T>&& other) noexcept :
count(other.size()),
buffer(other.data()) {
other.buffer = nullptr;
other.count = 0;
}
~dynamic_array() noexcept {
delete[] buffer;
}
dynamic_array& operator=(const dynamic_array<T>& rhs) noexcept {
resize(rhs.size());
std::copy(rhs.begin(), rhs.end(), begin());
return *this;
}
dynamic_array& operator=(const std::initializer_list<T>& list) noexcept {
resize(list.size());
std::copy(list.begin(), list.end(), begin());
return *this;
}
dynamic_array& operator=(dynamic_array<T>&& rhs) noexcept {
delete[] buffer;
buffer = rhs.data();
count = rhs.size();
rhs.buffer = nullptr;
rhs.count = 0;
return *this;
}
// iterators:
iterator begin() noexcept {
return buffer;
}
const_iterator begin() const noexcept {
return buffer;
}
iterator end() noexcept {
return begin() + size();
}
const_iterator end() const noexcept {
return begin() + size();
}
reverse_iterator rbegin() noexcept {
return reverse_iterator(end());
}
const_reverse_iterator rbegin() const noexcept {
return const_reverse_iterator(end());
}
reverse_iterator rend() noexcept {
return reverse_iterator(begin());
}
const_reverse_iterator rend() const noexcept {
return const_reverse_iterator(begin());
}
const_iterator cbegin() const noexcept {
return begin();
}
const_iterator cend() const noexcept {
return end();
}
const_reverse_iterator crbegin() const noexcept {
return rbegin();
}
const_reverse_iterator crend() const noexcept {
return rend();
}
// capacity:
size_type size() const noexcept {
return count;
}
size_type max_size() const noexcept {
return count;
}
bool empty() const noexcept {
return size() == 0;
}
// element access:
reference operator[](size_type n) {
return buffer[n];
}
const_reference operator[](size_type n) const {
return buffer[n];
}
reference at(size_type n) {
if (n < size()) {
return buffer[n];
}
throw std::out_of_range("The index " + std::to_string(n) + " is out of bounds.");
}
const_reference at(size_type n) const {
return at(n);
}
reference front() {
return *begin();
}
const_reference front() const {
return *cbegin();
}
reference back() {
return *(end() - 1);
}
const_reference back() const {
return *(cend() - 1);
}
// data access:
pointer data() noexcept {
return buffer;
}
const_pointer data() const noexcept {
return buffer;
}
// modifiers:
void fill(const T& value) noexcept {
std::fill(begin(), end(), value);
}
void swap(dynamic_array<T>& other) noexcept {
auto temp_data = other.data();
auto temp_size = other.size();
other.buffer = data();
other.count = size();
buffer = temp_data;
count = temp_size;
}
private:
size_type count;
T* buffer;
void resize(size_type new_count) noexcept {
if (size() != new_count) {
delete[] buffer;
buffer = new T[new_count];
count = new_count;
}
}
};
template<typename T>
inline bool operator==(const dynamic_array<T>& lhs, const dynamic_array<T>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template<typename T>
inline bool operator!=(const dynamic_array<T>& lhs, const dynamic_array<T>& rhs) {
return !(lhs == rhs);
}
template<typename T>
inline bool operator<(const dynamic_array<T>& lhs, const dynamic_array<T>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template<typename T>
inline bool operator>(const dynamic_array<T>& lhs, const dynamic_array<T>& rhs) {
return rhs < lhs;
}
template<typename T>
inline bool operator>=(const dynamic_array<T>& lhs, const dynamic_array<T>& rhs) {
return !(lhs < rhs);
}
template<typename T>
inline bool operator<=(const dynamic_array<T>& lhs, const dynamic_array<T>& rhs) {
return !(rhs < lhs);
}
Should I allow operations between arrays of different sizes, like swap and assignment? And if not, should I throw an exception or simply leave it as undefined behavior?
Edit: I have implemented most changes that were suggested, here is the updated version. I will leave the original code to not invalidate any answers.
const_reference at(size_type n) consthas an infinit recursion. It doesn't do what you expect. That's the wrong way round, try to implement the logic in theconstoverload and simply do astatic_caston this in thenon-constoverload. That's the usual approach for this kind (sure, you have toconst_castthe result back to non-const, but that is fine). \$\endgroup\$#pragma onceis not standard or universally recognized. Prefer classic header guards. \$\endgroup\$