Today I tried to learn how to make a simple garbage collector for a future project, it didn't need to be fast nor complex nor optimal, it just needed to work. After lots of searching and reading, I was able to make something work.
So, how can I improve it from here? At some point I guess I'll have to ditch the std::array
(the small size is just for testing purposes). Do I actually need to use new
and delete
, or can I just keep using STL containers?
For what I've read, I have a couple options: mark and compact, copying garbage collection, generational garbage collector. Which of them would be the more natural evolution from what I've done?
#include <array>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
template <typename T>
struct Handle {
bool marked {};
T value {};
Handle * next {};
};
template <typename T>
class Heap {
std::array<Handle<T>, 10> heap;
Handle<T> * root {};
Handle<T> * free_list {};
void push(Handle<T> * handle) {
handle->next = free_list;
free_list = handle;
}
void reset() {
for (size_t i = 0; i < heap.size(); ++i) {
heap[i].marked = false;
}
}
void mark(Handle<T> * handle) {
if (handle && !handle->marked) {
handle->marked = true;
mark(handle->next);
}
}
void sweep() {
free_list = nullptr;
for (size_t i = 0; i < heap.size(); ++i) {
if (!heap[i].marked) {
heap[i].value = {};
push(&heap[i]);
}
}
}
public:
Heap() {
for (size_t i = 0; i < heap.size(); ++i) {
push(&heap[i]);
}
}
template <typename U>
Handle<T> * allocate(U && u) {
if (!free_list) {
reset();
mark(root);
sweep();
if (!free_list) {
throw std::bad_alloc();
}
}
Handle<T> * handle = free_list;
free_list = free_list->next;
handle->value = std::forward<T>(u);
handle->next = nullptr;
return handle;
}
Handle<T> * keep_alive(Handle<T> * handle) {
handle->next = root;
root = handle;
return handle;
}
void signal() {
root = nullptr;
}
};
int main() {
Heap<std::string> heap;
auto a = heap.allocate("A");
auto b = heap.keep_alive(a);
a = heap.allocate("B");
auto c = heap.keep_alive(a);
a = heap.allocate("C");
a = heap.allocate("D");
a = heap.allocate("E");
cout << a->value << endl;
cout << b->value << endl;
cout << c->value << endl;
return 0;
}
std::shared_ptr
s won't work if there's cyclic references. \$\endgroup\$std::weak_ptrs
where you can introduce a cycle. That is what they are there for. \$\endgroup\$