For my game engine I needed a simple templated array class that can be safely passed across module boundaries. It's not supposed to be a replacement for std::vector and thus doesn't need all of its features. Instead it is intended to be used in the interface where binary compatibility is important. For malloc and free I use wrapper functions that are exported from my main core library but in order to have a working example I replaced them with the standard functions here.
#pragma once
#include <cstdint>
#include <cassert>
#include <type_traits>
//#include "memory.h"
// memory.h
//==========================================
namespace lel::core{
inline void* checked_malloc(std::size_t size){
auto mem = malloc(size);
if(!mem)
throw std::bad_alloc;
return mem;
}
inline void checked_free(void*& ptr){
if(ptr)
free(ptr);
ptr = nullptr;
}
}
//==========================================
namespace lel::core{
template<typename T>
struct Array{
using SizeType = std::int32_t; // Must be signed because the scripting language has no unsigned integers
using Iterator = T*;
using ConstIterator = const T*;
Array(SizeType initialSize = 0){
assert(initialSize >= 0);
data = checked_malloc(initialSize * sizeof(T) + sizeof(T));
arraySize = initialSize;
capacity = arraySize;
init(0, size());
}
Array(const Array& other){ *this = other; }
Array& operator=(const Array& other){
resize(other.size());
for(SizeType i = 0; i < size(); ++i)
(*this)[i] = other[i];
return *this;
}
~Array(){
deinit(0, size());
checked_free(data);
}
SizeType size() const noexcept{ return arraySize; }
T& operator[](SizeType index) noexcept{ return static_cast<T*>(data)[index]; }
const T& operator[](SizeType index) const noexcept{ return static_cast<T*>(data)[index]; }
void resize(SizeType newSize){
if(newSize > 0){
if(newSize <= arraySize){
arraySize = newSize;
deinit(newSize, arraySize - newSize);
}else{
if(newSize <= capacity){
init(arraySize, newSize - arraySize);
arraySize = newSize;
}else{
auto newCapacity = static_cast<SizeType>(newSize * 1.25f + 1.0f);
Array tmp;
tmp.data = checked_malloc(newCapacity * sizeof(T) + sizeof(T));
tmp.arraySize = newSize;
tmp.capacity = newCapacity;
for(SizeType i = 0; i < size(); ++i)
new(&tmp[i]) T{(*this)[i]};
deinit(0, size());
checked_free(data);
data = tmp.data;
tmp.data = nullptr;
arraySize = tmp.size();
tmp.arraySize = 0;
capacity = tmp.capacity;
tmp.capacity = 0;
}
}
}else{
deinit(0, size());
arraySize = 0;
}
}
void push_back(const T& t){
resize(size() + 1);
back() = t;
}
void clear(){
deinit(0, size());
checked_free(data);
arraySize = 0;
capacity = 0;
data = checked_malloc(sizeof(T)); // For the end iterator
}
T& back() noexcept{ return (*this)[size() - 1]; }
const T& back() const noexcept{ return (*this)[size() - 1]; }
void pop_back(){ resize(size() - 1); }
void emplace_back(){ push_back(T{}); }
Iterator begin() noexcept{ return &(*this)[0]; }
ConstIterator begin() const noexcept{ return &(*this)[0]; }
Iterator end() noexcept{ return &(*this)[size()]; }
ConstIterator end() const noexcept{ return &(*this)[size()]; }
private:
void* data = nullptr;
SizeType arraySize = 0;
SizeType capacity = 0;
void init(SizeType startIndex, SizeType num){
for(SizeType i = 0; i < num; ++i)
new(&(*this)[startIndex + i]) T{};
}
void deinit(SizeType startIndex, SizeType num){
if constexpr(!std::is_trivially_destructible<T>{}){
for(SizeType i = 0; i < num; ++i)
std::destroy_at(&(*this)[startIndex + i]);
}
}
static constexpr void static_assertions() noexcept{
static_assert(std::is_standard_layout<Array<T>>{});
static_assert(std::is_standard_layout<T>{});
}
};
}
```
resize
function. \$\endgroup\$ – Mike Borkland Feb 28 '19 at 12:52newSize
<=arraySize
. \$\endgroup\$ – Mike Borkland Feb 28 '19 at 12:59