Say I have this basic setup:
#include <list>
struct Linker
{
Linker* to;
//some Linker specific stuff
};
struct Holder
{
std::list<Linker> links;
//some Holder specific stuff
//If I access the "to" variable in a Linker, I want to be able to access the data of the Holder that contains the Linker
};
So simple objects that are stored in lists that point to other objects, in this case of the same type. When I access to in a Linker and grab another Linker, I want to be able to figure out what Holder that other Linker is in as well as access that Linker’s data.
I should specify, after reading Will’s answer, that I’m not trying to simulate another list, exactly. In the real implementation, not every item in Holder’s list will be a Linker, and I still need to access everything through the list (with an order specific iteration). But the list handles that, so that’s fine. I need a level of sporadic linking between items typically in different lists/Holders altogether on top of the list structure in the Holder class.
What’s the best way to do this? I’ve considered putting a Holder reference or pointer in each Linker, but let’s say I want to keep structs like Linker separate from data about their container (using them in other contexts where there is no container sometimes, want to construct outside with no container info then have Holder with an add function to setup that info, etc).
I’ve considered using std::pair with a Holder reference or pointer as the other type, or extending Linker to something like HeldLinker with the info. Also have thought of using something like this for more specific variable names than std::pair:
template<typename R, typename O> struct refwrap
{
R& ref;
O obj;
refwrap(R& ref, const O& obj) : ref(ref), obj(obj) {}
};
In all of these cases, the Linker* to and std::list<Linker> links would be modified to use the appropriate class (std::pair, HeldLinker, refwrap, whatever). However, it seems all of these “solutions” would cause a function like this:
void Holder::addlink(const Linker& link)
{
//wrap link into whatever will hold a Holder reference/pointer
//add to links list
}
To copy the incoming link object twice: when creating the wrapper object of whatever type and then again when adding that wrapper to the std::list object. Not doing any of these wrapper methods would limit it to a single copy. Is there a way to have my cake and eat it too, and have the single requisite copy for adding while also letting Holder wrap up Linker objects so their references also contain the Holder object they’re in? Or is there some better “addressing” scheme I could use for Linker that doesn’t fit this paradigm while accomplishing the same thing?
Preferably, it would be something easily extensible, so if I were to add in this:
struct HolderHolder
{
std::list<Holder> holders;
};
And it set it up with a similar relationship as Linker to Holder, it could be done without getting really crazy type names. And for HolderHolderHolder, etc.
You want the most efficient way to store a reference to an object in an object?
The most efficient way is a pointer. It is likely 4 or 8 bytes, and likely doesn’t affect the alignment of the next item in the struct, so is therefore fine.
You can save bytes by having numbers that look up owner objects, but this is likely actually not saving real bytes in the struct allocation, its just introducing padding.
Setting a pointer takes a memory word write. The destination is likely adjacent to other writes you are doing during initialization; its unlikely to impact performance even in a tight loop.
An alternative to using
stl::listis to put the list nodes themselves inside the data-structure.This is common in high-performance environments e.g. kernels. Here’s a description of the Linux kernel one.
By placing the next (and possibly previous) pointer (or XORing them to save space) inside the struct, no separate memory allocation is required.
This means that a object in the list can only ever be in one list at a time, but your
tofield means this anyway so this does not constrain you.You can have a convention that the head is actually the owning object; this obviously takes O(n) to discover but perhaps you need to discover the
toonly occasionally?So to summarize:
stl::listat all, but rather just havenext(andprev, or perhaps XOR them) in the node itselftofield explicitly