The idea is to use linked lists as members of the linked objects. An object may have multiple such members i.e. it can be a member of multiple lists.
The pros are:
- No extra heap allocations
- Locality in memory/cache
- When the object is destroyed it will unlist itself
Managing the list in a member requires a way to go from the member to the enclosing object.
What I managed to create is the following:
#include <cstddef>
#include <memory>
#include <iostream>
template <typename S, typename Member>
struct listing {
using type = listing<S, Member>;
S* outer_this() {
return get_outer_this(Member(), this);
}
type* prev{};
type* next{};
void enlist_after(type* prev) {
unlist();
this->prev = prev;
if (prev) {
this->next = prev->next;
if (this->next) {
this->next->prev = this;
}
prev->next = this;
}
}
void unlist() {
if (this->next) {
this->next->prev = this->prev;
}
if (this->prev) {
this->prev->next = this->next;
}
this->prev = nullptr;
this->next = nullptr;
}
~listing() {
unlist();
}
};
struct MyStruct {
int i;
MyStruct(int i) : i(i) {}
struct a_listing_member {};
struct b_listing_member {};
listing<MyStruct, a_listing_member> a_listing;
listing<MyStruct, b_listing_member> b_listing;
};
static MyStruct* get_outer_this(MyStruct::a_listing_member, auto* m) {
uintptr_t mptr = reinterpret_cast<uintptr_t>(m);
uintptr_t this_ptr = mptr - offsetof(MyStruct, a_listing);
return reinterpret_cast<MyStruct*>(this_ptr);
}
static MyStruct* get_outer_this(MyStruct::b_listing_member, auto* m) {
uintptr_t mptr = reinterpret_cast<uintptr_t>(m);
uintptr_t this_ptr = mptr - offsetof(MyStruct, b_listing);
return reinterpret_cast<MyStruct*>(this_ptr);
}
int main() {
MyStruct s1(1), s2(2), s3(3);
auto a_list = &(s2.a_listing);
s1.a_listing.enlist_after(&(s2.a_listing));
s3.a_listing.enlist_after(&(s1.a_listing));
auto b_list = &(s1.b_listing);
s2.b_listing.enlist_after(&(s1.b_listing));
s3.b_listing.enlist_after(&(s2.b_listing));
std::cout << "a_list" << std::endl;
while (a_list) {
std::cout << a_list->outer_this()->i << std::endl;
a_list = a_list->next;
}
std::cout << "b_list" << std::endl;
while (b_list) {
std::cout << b_list->outer_this()->i << std::endl;
b_list = b_list->next;
}
}
But I'm sure this can be done much better.
I had to introduce extra structs to specialize the template, because the member variable itself is not yet declared when I need to declare it. All my attempts at declaring it ahead of time failed.
The example is simplified. In reality this will be more complex i.e. more than a simple linked list. But the principle is the same.