A common approach to 2) is the database approach ("data-oriented design") where you basically have tables and replace pointers by offsets into these tables.
That might not work if the situation is very uncontrolled and objects live and die very quickly. But in most cases you can just let die a few objects, and every once in a while do "garbage collection" manually by renumbering the still-alive objects to be consecutively indexed.
Usually the result is very clean, performant and modular code.
It's clean and modular for all the reasons that E.F. Codd preached all his life.
It's performant because the tables approach is not micro-managing allocations - each table is only one allocation.
You will be hard pressed to detect a difference of (single array + relative index) to raw pointers (= absolute index). There's even machine level support for relative addressing.
You also write most of your code to operate on slices (tables or contiguous subsets of tables) instead of only one row per function call. Mike Acton rightfully says "where there's one, there's many". This approach is obviously great for performance because it avoids function-call overhead and because it's cache-friendly.
By the way, what's Rust's story to avoid referencing dead items in these tables?
That might not work if the situation is very uncontrolled and objects live and die very quickly. But in most cases you can just let die a few objects, and every once in a while do "garbage collection" manually by renumbering the still-alive objects to be consecutively indexed.
Usually the result is very clean, performant and modular code.
It's clean and modular for all the reasons that E.F. Codd preached all his life.
It's performant because the tables approach is not micro-managing allocations - each table is only one allocation. You will be hard pressed to detect a difference of (single array + relative index) to raw pointers (= absolute index). There's even machine level support for relative addressing.
You also write most of your code to operate on slices (tables or contiguous subsets of tables) instead of only one row per function call. Mike Acton rightfully says "where there's one, there's many". This approach is obviously great for performance because it avoids function-call overhead and because it's cache-friendly.
By the way, what's Rust's story to avoid referencing dead items in these tables?