blob: db4a102f90a10abe18cc3dd25d4c08f13d4ec327 [file] [log] [blame]
#ifndef AOS_CONTAINERS_RESIZEABLE_BUFFER_H_
#define AOS_CONTAINERS_RESIZEABLE_BUFFER_H_
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <cstdlib>
#include <memory>
#include "absl/log/check.h"
#include "absl/log/log.h"
namespace aos {
// Kind of like a subset of vector<uint8_t>, but with less destructor calls.
// When building unoptimized, especially with sanitizers, the vector<uint8_t>
// version ends up being really slow in tests.
//
// F is the allocator used for reallocating the memory used by the buffer.
template <class F>
class AllocatorResizeableBuffer {
public:
AllocatorResizeableBuffer() = default;
AllocatorResizeableBuffer(const AllocatorResizeableBuffer &other) {
*this = other;
}
AllocatorResizeableBuffer(AllocatorResizeableBuffer &&other) {
*this = std::move(other);
}
AllocatorResizeableBuffer &operator=(const AllocatorResizeableBuffer &other) {
resize(other.size());
memcpy(storage_.get(), other.storage_.get(), size());
return *this;
}
AllocatorResizeableBuffer &operator=(AllocatorResizeableBuffer &&other) {
std::swap(storage_, other.storage_);
std::swap(size_, other.size_);
std::swap(capacity_, other.capacity_);
return *this;
}
uint8_t *data() { return static_cast<uint8_t *>(storage_.get()); }
const uint8_t *data() const {
return static_cast<const uint8_t *>(storage_.get());
}
uint8_t *begin() { return data(); }
const uint8_t *begin() const { return data(); }
uint8_t *end() { return data() + size(); }
const uint8_t *end() const { return data() + size(); }
uint8_t &at(int index) { return *(data() + index); }
const uint8_t &at(int index) const { return *(data() + index); }
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
bool empty() const { return size_ == 0; }
void reserve(size_t new_size) {
if (new_size > capacity_) {
Allocate(new_size);
}
}
void resize(size_t new_size) {
if (new_size > capacity_) {
Allocate(new_size);
}
size_ = new_size;
}
void erase_front(size_t count) {
if (count == 0) {
return;
}
CHECK_LE(count, size_);
memmove(static_cast<void *>(data()), static_cast<void *>(data() + count),
size_ - count);
size_ -= count;
}
void push_back(uint8_t x) {
if (size_ == capacity_) {
Allocate(std::max<size_t>(16, size_ * 2));
}
*end() = x;
++size_;
CHECK_LE(size_, capacity_);
}
private:
// We need this silly function because C++ is bad, and extensions to it which
// we work with make it a true nightmare.
//
// (a) You can't easily write out the signature of free because it depends on
// whether exceptions are enabled or not. You could use decltype, but see (b).
//
// (b) You can't easily write &free because CUDA overloads it with a
// __device__ version. You could cast to the appropriate version, but see (a).
//
// There's probably some kind of SFINAE thing which could find the matching
// signature from a set of choices, and then we could just
// static_cast<TheSignature>(&free). However, that sounds like a nightmare,
// especially because it has to conditionally enable the part mentioning CUDA
// identifiers in the preprocessor. This little function is way simpler.
static void DoFree(void *p) { free(p); }
void Allocate(size_t new_capacity) {
void *const old = storage_.release();
void *new_ptr = F::Realloc(old, capacity_, new_capacity);
CHECK(new_ptr != nullptr);
storage_.reset(new_ptr);
capacity_ = new_capacity;
}
std::unique_ptr<void, decltype(&DoFree)> storage_{nullptr, &DoFree};
size_t size_ = 0, capacity_ = 0;
};
// An allocator which just uses realloc to allocate.
class Reallocator {
public:
static void *Realloc(void *old, size_t /*old_size*/, size_t new_capacity) {
return realloc(old, new_capacity);
}
};
// Allocates aligned memory.
template <size_t alignment>
class AlignedReallocator {
public:
static void *Realloc(void *old, size_t old_size, size_t new_capacity) {
void *new_memory = std::aligned_alloc(alignment, new_capacity);
if (old) {
memcpy(new_memory, old, old_size);
free(old);
}
return new_memory;
}
};
// A resizable buffer which uses realloc when it needs to grow to attempt to
// avoid full coppies.
class ResizeableBuffer : public AllocatorResizeableBuffer<Reallocator> {};
} // namespace aos
#endif // AOS_CONTAINERS_RESIZEABLE_BUFFER_H_