Skip to content

Instantly share code, notes, and snippets.

@eyelash
Created February 16, 2020 16:13
Show Gist options
  • Save eyelash/87a3fe849b31a37c58eca4ef398a71dc to your computer and use it in GitHub Desktop.
Save eyelash/87a3fe849b31a37c58eca4ef398a71dc to your computer and use it in GitHub Desktop.
#include <cstdlib>
#include <atomic>
#include <memory>
template <class T> class Storage {
T* storage;
const std::size_t capacity;
std::atomic<std::size_t> size;
std::atomic<std::size_t> reference_count;
public:
Storage(std::size_t capacity): capacity(capacity), size(0), reference_count(0) {
storage = static_cast<T*>(std::malloc(capacity * sizeof(T)));
}
Storage(const Storage&) = delete;
~Storage() {
for (std::size_t i = 0; i < size; ++i) {
storage[i].~T();
}
std::free(storage);
}
Storage& operator =(const Storage&) = delete;
std::size_t get_capacity() const {
return capacity;
}
const T* get(std::size_t index) const {
return storage + index;
}
bool try_insert(std::size_t index, const T& element) {
if (index < capacity && size.compare_exchange_strong(index, index + 1)) {
new (storage + index) T(element);
return true;
}
return false;
}
void retain() {
reference_count.fetch_add(1);
}
bool release() {
return reference_count.fetch_sub(1) == 1;
}
};
template <class T> class ImmutableVector {
Storage<T>* storage;
std::size_t size;
ImmutableVector(Storage<T>* storage, std::size_t size): storage(storage), size(size) {
storage->retain();
}
public:
ImmutableVector(): ImmutableVector(new Storage<T>(4), 0) {}
ImmutableVector(const ImmutableVector& vector): ImmutableVector(vector.storage, vector.size) {}
~ImmutableVector() {
if (storage->release()) {
delete storage;
}
}
ImmutableVector& operator =(const ImmutableVector& vector) {
if (vector.storage != storage) {
if (storage->release()) {
delete storage;
}
storage = vector.storage;
storage->retain();
}
size = vector.size;
return *this;
}
ImmutableVector insert(std::size_t index, const T& element) const {
if (storage->try_insert(index, element)) {
return ImmutableVector(storage, size + 1);
}
const std::size_t new_capacity = std::max<std::size_t>(size * 2, 4);
Storage<T>* new_storage = new Storage<T>(new_capacity);
for (std::size_t i = 0; i < index; ++i) {
new_storage->try_insert(i, *storage->get(i));
}
new_storage->try_insert(index, element);
for (std::size_t i = index; i < size; ++i) {
new_storage->try_insert(i + 1, *storage->get(i));
}
return ImmutableVector(new_storage, size + 1);
}
ImmutableVector push_back(const T& element) const {
return insert(size, element);
}
ImmutableVector erase(std::size_t index) const {
const std::size_t new_capacity = std::max<std::size_t>((size - 1) * 2, 4);
if (index == size - 1 && new_capacity > storage->get_capacity() / 2) {
return ImmutableVector(storage, size - 1);
}
Storage<T>* new_storage = new Storage<T>(new_capacity);
for (std::size_t i = 0; i < index; ++i) {
new_storage->try_insert(i, *storage->get(i));
}
for (std::size_t i = index + 1; i < size; ++i) {
new_storage->try_insert(i - 1, *storage->get(i));
}
return ImmutableVector(new_storage, size - 1);
}
ImmutableVector pop_back() const {
return erase(size - 1);
}
const T& operator [](std::size_t index) const {
return *storage->get(index);
}
const T* begin() const {
return storage->get(0);
}
const T* end() const {
return storage->get(size);
}
};
@tvaneerd
Copy link

tvaneerd commented Feb 17, 2020

You might want to comment the code. It is very subtle that storage size is separate from vector size and that the underlying reasoning is so that push_back can use the same vector as the original since the original will never access past its own size.

I wonder if using something like std::span as part of the implementation would make this more obvious. (also more compile time, but you wouldn't need std::span, you could use a trimmed down thing of your own)

@tvaneerd
Copy link

Also, it isn't exception safe - you can leak Storage on insert/erase if T throws when copied.
Not sure if you are worried about exception safety, but it would be nice to have the same safety as std::vector.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment