I have 300+ classes. They are related in some ways.
For simplicity, all relation are 1:1.
Here is a sample diagram.
(In real case, there are around 50 relation-pairs.)
Note: For some instances, some relation may not exist.
For example, some hen
s don't relate to any food
.
Note2: No link = never, e.g. every egg
doesn't relate to any cage
.
Such relation will never be added/removed/queried.
Question:
How to store relation between them elegantly?
All 4 of my ideas (below) seem to have disadvantages.
Here is a related question but with 1:N and only 1 relation.
My poor solutions
These are semi-pseudo-codes.
Version 1 Direct
My first thought is to add pointer(s) to each other.
Chick.h:-
class Egg;
class Food;
class Chick{ Egg* egg; Food* food;}
Hen.h:-
class Egg; class Cage; class Food;
class Hen{ Egg* egg; Cage* cage; Food* food;}
It is very cheap to add/remove relation and query, e.g. :-
int main(){
Hen* hen; ... Egg* egg=hen->egg;
}
It works good, but as my program grow, I want to decouple them.
Roughly speaking, Hen.h
should not contain word Egg
, and vice versa.
There are many ideas, but none seems very good.
I will show a brief snippet for each work-around then summarizes pros & cons at the end of question.
Version 2 Hash-map
Use std::unordered_map
.
It becomes a bottle neck of my program. (profiled in release mode)
class Egg{}; class Hen{}; //empty (nice)
.....
int main(){
std::unordered_map<Hen*,Egg*> henToEgg;
std::unordered_map<Egg*,Hen*> eggToHen;
....
Hen* hen; ... Egg* egg=henToEgg[hen];
}
Version 3 Mediator-single
Store every relation in a single big mediator for every entity.
Waste a lot of memory for empty slots (e.g. Egg
has henFood_hen
slot).
Total waste = type-of-relation-pair
*2*4 bytes (if run at 32 bits) in every entity.
class Mediator {
Egg* eggHen_egg=nullptr;
Hen* eggHen_hen=nullptr;
Hen* henFood_hen=nullptr;
Food* henFood_food=nullptr;
//... no of line = relation * 2
};
class Base{public: Mediator m;};
class Egg : public Base{}; //empty (nice)
class Hen : public Base{};
int main(){
Hen* hen; ... Egg* egg=hen->eggHen_egg;
}
Version 4 Mediator-array (similar as 3)
Try to standardize - high flexibility.
class Mediator {
Base* ptrLeft[5];
Base* ptrRight[5];
};
class Base{public: Mediator m;};
class Egg : public Base{}; //empty (nice)
class Hen : public Base{};
int main(){
enum RELA_X{RELA_HEN_EGG,RELA_HEN_CAGE,RELA_EGG_CHICK, .... };
Hen* hen; ...
Egg* egg=hen->m.ptrRight[RELA_HEN_EGG];
//^ get right of "hen-egg" === get "egg" from "hen"
//^ can be encapsulated for more awesome calling
}
Pros & Cons
Green (+
) are good. Red (-
) are bad.
Edit: I am using Entity-Component for a 60fps game.
It is a persistent database : a single instance used for the entire life of a game.
Edit2: All of the relation are weak relation rather than is-a or strong std::unique_ptr
ownership. (Thank Walter)
- A
hen
is in acage
.
Somehens
are not in anycage
, and somecages
are empty. - A
chick
come from anegg
.
However, somechicks
didn't come from anyegg
(they are just dropped from sky),
and someeggs
are not lucky enough to becomechick
. - A
hen
and achick
are eating a (probably same) plate offood
.
Somefood
plates are just prepared but not served.
Edit3: Assign an integer id for each object can be a good idea.
(Thank Oliv, ahoxha, and Simone Cifani)
Edit4:: No need to provide a compilable code, just an essential part / concept is enough.