13

I have 300+ classes. They are related in some ways.

For simplicity, all relation are 1:1.
Here is a sample diagram.

enter image description here (In real case, there are around 50 relation-pairs.)

Note: For some instances, some relation may not exist.
For example, some hens 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. enter image description here

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 a cage.
    Some hens are not in any cage, and some cages are empty.
  • A chick come from an egg.
    However, some chicks didn't come from any egg (they are just dropped from sky),
    and some eggs are not lucky enough to become chick.
  • A hen and a chick are eating a (probably same) plate of food.
    Some food 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.

Community
  • 1
  • 1
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • The problem in your code may be architectural. There is certainly a common algorithm pattern that uses these relations. Then an algorithm pattern can either be implemented using meta-programming or polymorphism or both. In your case, polymorphism may be more adequate since there could have some factorial growth of relations. – Oliv Apr 13 '17 at 11:22
  • @Oliv Thank. I am interested in "polymorphism". Is that approach has some advantage over my solutions? ..... I don't think my code has architectural problem. It is a game, so there are some dynamic relation between objects. So far, I have gain a great deal of flexibility + maintainability by grouping relation together i.e. use solution 4. Is it a bad idea? I don't know how commercial game manage it. – javaLover Apr 13 '17 at 12:29
  • 2
    I do not know what you plan to do with those relations. Only you can judge. You are right to think that what makes the efficiency of an algorithm is data organisation. But I am surprised you do not know about "polymorphism". You should realy learn about it. And also meta-programming. Once you will have learnt about it (it should be easy if you know java), I am sure you will find a very smart solution for your problem. And a hint for later, in big games, efficiency is reached by sorting tables of abstract type pointers by their dynamic type... Welcome to a new world! – Oliv Apr 13 '17 at 17:18
  • @Oliv I tend to use composition instead of inheritance, because I faced trauma of dread-diamond + cost of `dynamic_cast` + cost of virtual calling. ..... I guess "meta-programming" = macro. ..... You last sentence is very useful. May you provide more information about "sorting tables", please? Thank. – javaLover Apr 18 '17 at 04:47
  • 1
    meta-programming~template... Virtual calls are not that expensive. If you repeat a virtual call, the devirtualization cost decreases. But in a loop, you should not repeat an operation that does not depend on the looping parameter, this applies to the devirtualization process. – Oliv Apr 18 '17 at 07:02
  • 1
    (continuation of preceding comment) So if you a huge table of pointer to abstract type, you should organize your table so that all pointer that points to objects with the same dynamic type, are contiguous. (In fact you should do it when you build objects, direct call on fragmented data can be more expensive than virtual call on non-fragmented data.) Then you implement a kind of double dispatch or a visitor pattern so that you only perform a virtual call on the first elements of the contiguous area and then perform a static cast to all the pointer inside the loop... – Oliv Apr 18 '17 at 07:02
  • @Oliv After re-reading it several times, I can see a glimpse of solution in your comments. I have to think more about it. It may be something really great for which I have searched for several months. Thank a lot. – javaLover Apr 18 '17 at 10:28
  • It's not clear what the **logic** behind your relation is (w/o that knowledge answers will be guess work). Are these *is-a* or *has-a*, relations, or both or neither? – Walter Apr 19 '17 at 09:12
  • @Walter Usually, they are *has-a*. I will update the question, thank. – javaLover Apr 19 '17 at 09:14

6 Answers6

4

Based on the requirements, if you have only one-to-one relations, then it sounds to me like a graph. In this case, if it is densely populated (there are many relations), I would use the matrix representation of the graph. In the tables below, I have associated numbers 0 through 4 to the entities (Hen, Cage, Food, Egg and Chick) respectively. If the relation Hen - Egg exists, then the matrix will have a 1 at the position matrix[0][3], if it doesn't then the value would be 0 (you can choose values of your choice to decide how to tell when the relation exists or doesn't). If the relations are undirected, then you only need one side of the matrix (the upper triangle, for example).

+---------------------------------+
| Hen | Cage | Food | Egg | Chick |
+---------------------------------+
|  0  |  1   |  2   |  3  |   4   |
+---------------------------------+

      0   1   2   3   4
    +--------------------+
  0 | 0 | 1 | 0 | 1 | 1  |
    +---+---+---+---+----+
  1 | 0 | 0 | 0 | 1 | 1  |
    +---+---+---+---+----+
  2 | 0 | 0 | 0 | 0 | 1  |
    +---+---+---+---+----+
  3 | 0 | 0 | 0 | 0 | 1  |
    +---+---+---+---+----+
  4 | 0 | 0 | 0 | 0 | 0  |
    +--------------------+

The downside of this solution hides in the memory usage, especially if the matrix contains a lot of 0's (relations that don't exist); you would be unnecessarily occupying a lot of space. In this case you may use the linked-list representation of the graphs.

ahoxha
  • 1,919
  • 11
  • 21
  • Thank for the nice table, and the good link. The first solution eats memory same as my 3rd-4th approach, but yours is more "decouple". I will not use link-list though (search O(n) + cache miss if not delicately managed). – javaLover Apr 19 '17 at 12:27
  • Yes true. The advantage of this is the decoupling that you get, you can very easily add/remove entities/relations. – ahoxha Apr 19 '17 at 12:30
  • Hi, I just created an after-story of this question, perhaps, you may have some nice ideas. https://stackoverflow.com/questions/55898783/ – javaLover Apr 29 '19 at 07:51
3

My suggestion:

  1. Add a common base class.
  2. Add a list of parents and children to the base class.
  3. Add functions to add, remove, insert, query parents and children to the base class.
  4. Add higher level non-member functions as needed.

class Base
{
   public:
      virtual ~Base();

      // Add "child" to the list of children of "this"
      // Add "this" to the list of parents of "child"
      void addChild(Base* child);

      // Remove "child" from the list of children of "this"
      // Remove "this" from the list of parents of "child"
      void removeChild(Base* child);                                 

      std::vector<Base*>& getParents();
      std::vector<Base*> const& getParents() const;

      std::vector<Base*>& getChildren();
      std::vector<Base*> const& getChildren() const;

   private:
    std::vector<Base*> parents_;
    std::vector<Base*> chilren_;    
};

Now you can implement higher level functions. E.g.

// Call function fun() for each child of type T of object b.
template <typename T>
void forEachChild(Base& b, void (*fun)(T&))
{
   for ( auto child, b.getChildren() )
   {
      T* ptr = dynamic_cast<T*>(child);
      if ( ptr )
      {
         fun(*ptr);
      }
   }
}

To query the unique egg from a hen, you could use a generic function template.

template <typename T>
T* getUniqueChild(Base& b)
{
   T* child = nullptr;
   for ( auto child, b.getChildren() )
   {
      T* ptr = dynamic_cast<T*>(child);
      if ( ptr )
      {
         if ( child )
         {
            // Found at least two.
            // Print a message, if necessary.
            return NULL;
         }

         child = ptr;
      }
   }

   return child;
}

and then use it as:

hen* henptr = <get a pointer to a hen object>;
egg* eggptr = getUniqueChild<egg>(*henptr);
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Sir R Sahu, thank that you are here. How I can query the unique `egg` from a certain `hen`? How the code would look like? – javaLover Apr 19 '17 at 02:19
  • cost = `O( henPtr->getChildren().size() )`? It is 3 in this case (connect to `cage` `food` and `egg`), and it will inherently slower as the chart grow. – javaLover Apr 19 '17 at 03:10
  • @javaLover, it will get slow only if the number of parents/children of an object grows large. It won't be a problem if the entire chart grows while the number of parents/children of individual objects remains small. – R Sahu Apr 19 '17 at 04:42
  • Thank, it is unexpected though. All of my poor solutions are O(1) if not consider memory/cache. I understand what you mean. Yes, it will win in some cases. :) – javaLover Apr 19 '17 at 04:46
  • 2
    your design lacks clear ownership semantics for the children and parents. I suggest to use `std::unique_ptr` for the children but observing `const Base*` for the parents. – Walter Apr 19 '17 at 09:07
  • @Walter The relation is not ownership. Sorry if the question is not clear. It is really just "relation". For example, a `hen` is given a plate of `food`, but `food` is not owned by that `hen` . Some instances of `food` are not even related to any `hen` e.g. they are just prepared but not served. – javaLover Apr 19 '17 at 09:23
2

There must be some game-related logic behind your relations. Sometimes relations can be uni-directional, sometimes one-to-many etc. How to implement them highly depends on the logic and architecture.

1) typical is-a relation, e.g. your egg -> food case. Sounds like it's a simple inheritance, when Egg class should be derived from Food class

2) aggregation, e.g. hen -> egg case. Here you know that each hen can have (produce?) one or more eggs, this is part of your game logic and this info deserves to be hardcoded, for convenience, readability and performance: e.g. hen.eggs.count(). In this case you know what (almost concrete) type is expected, so declaration looks like

class Hen:
    List<Egg> eggs;

I'm not sure decoupling here is beneficial as to use eggs you do need to know about Egg class.

3) abstract components. On the abstract level of a game engine, when you don't have any specific game logic (or don't want to use it). E.g. Unity3D Components, or Unreal Engine Actors. Their main purpose is to help you to organise your stuff in an hierarchy, so you can clone part of your game world (e.g. compound building consisting of many parts), move it, reorganise etc. You have a base class for these components and you can enumerate a component children or query a particular child by its name or some ID. This method is abstract and helps to decouple game engine logic from a particular game logic. It doesn't mean it's applicable only for re-usable game engines. Even games that were built from scratch not using a 3rd-party game engines usually have some "game engine" logic. Usually such component model involves some overhead, e.g. cage.get_all_components("hen").count() - much more typing, less readable and there're some runtime overhead to enumerate only hens and count them.

class Component:
    List<Component> children;

As you can see here you don't have any dependencies between classes that derive from Component. So ideally dealing with children you don't need to know their concrete type and abstract Component is enough to do generic things like to specify a place in your game world, delete or re-parent it. Though on practice it's common to cast it to a concrete type, so decoupling here is just to separate game engine logic from game logic.

It's OK to combine all three methods.

Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
  • Thank. **1)** promotes high couple but superior than 1st of mine. ... **2)** This is 1:1 relation, so it is N/A (?). ... **3)** I can't query the unique `Egg` from `Hen`, right? – javaLover Apr 19 '17 at 02:17
  • 1) no surprise high couple of logic is reflected in implementation, and `is a` relation is. 2) it was just an example (more flexible, still can be used for 1:1 relation), 1:1 would simply look like `class Hen: Egg egg;`. 3) you can execute whatever query you need, just implement it, e.g. `Component::get_child_by_GUID(GUID id)` – Andriy Tylychko Apr 19 '17 at 10:04
  • as a side note, I'd highly recommend to use a 3rd-party engine, existing products are good and can safe tons of effort. Unity3D is simpler to start with and is popular for indie games while Unreal Engine is often used for AAA games. Even if it's just an exercise in sake of exercise, check their approaches, this would intensify your learning – Andriy Tylychko Apr 19 '17 at 10:10
  • I agree. What keyword would you recommend? May you provide some links? I have tried "unreal OR unity relationship game object" for some days. I can find only solution for entity-component relation (easy), while I only have problem with entity-entity relation. – javaLover Apr 19 '17 at 11:00
  • I'd try `Unity3D component model` – Andriy Tylychko Apr 19 '17 at 11:11
  • After skimming, I think the keyword lead to the same area as ECS (Component-based Entity Systems) i.e. avoid deep inheritance by using composition. The concept is also about managing relation between entity and component (easy). I think I have already absorbed this technology. Those articles (always) do not describe how to manage relation between 2 entities. – javaLover Apr 19 '17 at 11:15
  • game logic relations that you mention are strong enough to use explicit composition as in (2). no need to try to loosen coupling here as to use that relation you'd need to couple your classes anyway. sorry for repeating – Andriy Tylychko Apr 19 '17 at 15:26
  • I am just a beginner, sometimes need to be repeatedly taught by pro. (However, I don't see how this case is a repeat.) Thank for dedicating your time. Don't hesitate to ping me if you have something to teach. :) – javaLover Apr 19 '17 at 16:06
1

There is and can be no good answer to this question, since your algorithm is not known. What can be said in general, is that you want locality on your data and indirection is always a way to break it.

If you have an algorithm, that works on hens, you want them to be packed as closely as possible and ideally to be linear in memory, for the maximum cache hit rate.

If your algorithm needs to work on the relation, between hens and eggs. They need to be local. This can not be achieved by saving them as pointers in the hens, but you need an array of all hen <-> egg relation.

You see, it relly depends on what you intend to do. If you really aim to gain high permance, you have to prevent deep indiretion. Every pointer, you try to resolve may trash a cache line. If your cpu, is only chasing them down, the performance will be very low.

OutOfBound
  • 1,914
  • 14
  • 31
  • (answer "your algorithm is not known.") It is for game, but I don't think it is so important. .... I need some random add/remove and query in random order. ...... I am also using pool, and I can't sacrifice data locality for relation. Locality has already depended on something more important. – javaLover Apr 13 '17 at 09:01
  • There is not much advise, someone can give you, if the access pattern is not known (since performance is a consideration for you). If you are able to establish a total order on your items, you can use std::map instead of unordered map. That will reduce complexity from O(n) to O(log(n)) on access. Other than that, i would not suggest you to use any of the mediator pattern. They waste memory and erase type information at compile time. That may introduce additional errors and cost runtime for the virtual method lookup (but not much). – OutOfBound Apr 13 '17 at 09:20
1

I followed an approach similar to that of R Sahu to create a raw persistence library. In my implementation each entity must implement a base interface, called IEntity. The entity basically contains a vector of fields, represented by the interface IField, like follows:

typedef shared_ptr<IField>  field_ptr;
typedef vector<field_ptr> fields_vec;

class IEntity
{
public: 

    virtual string const& getEntityName() = 0;
    virtual bool allowDuplicates() = 0;
    virtual fields_vec const& getFields() = 0;
    virtual void setFieldValue(string fieldName, string fieldValue) = 0;
    //callback is called after queries to fill the queryResult map (fieldName, fieldValue)
    virtual void callback(map<string, string> queryResult) = 0;
};

class IField
{
public:
    typedef enum
    {
        INTEGER,
        FLOAT,
        REAL,
        NUMERIC,
        DATE,
        TIME,
        TIMESTAMP,
        VARCHAR
    } Type;

    virtual string const&  getName()              const = 0;
    virtual Type           getType()              const = 0;
    virtual string const&  getValue()             const = 0;    
    virtual bool           isPrimaryKey()         const = 0;
    virtual bool           isForeignKey()         const = 0;
    virtual bool           isUnique()             const = 0;
    virtual bool           isAutoIncrement()      const = 0;
    virtual bool           isNotNull()            const = 0;
    virtual int            getVarcharSize()       const = 0;
    virtual void           setValue(string value)       = 0;
    // Manage relations
    virtual IEntity* const getReferenceEntity()   const = 0;
    virtual string const&  getReferenceField()    const = 0; 
};

class CField : 
    public IField
{
public:
    CField(string name, Type type, bool primaryKey, bool unique, bool autoincrement, 
        bool notNull = false, int varcharSize = 0)
    {
        ...
    }

    CField(string name, Type type, IEntity* const referenceEntity, string const& referenceField,
        bool notNull = false, int varcharSize = 0)
    {
        ...
    }

    ...
};

Then, I have an entity manager that provides basic persistence functions:

class CEntityManager
{
public:
    CEntityManager();
    virtual ~CEntityManager();

    //--------------------------------------------//
    //Initializes db and creates tables if they not exist 
    bool initialize(string sDbName, vector<shared_ptr<IEntity>> const& entities);

    //--------------------------------------------//
    //Returns a shared_ptr instance of IField   
    field_ptr createField(string name, IField::Type type,
        bool primaryKey = false, bool unique = false, bool autoincrement = false, bool notNull = false, int varcharSize = 0);

    //--------------------------------------------//
    //Returns a shared_ptr instance of IField, 
    //When the field represents a foreign key, 'referenceField' specifies the column referenced to the 'referenceEntity'
    // and 'updateBy' specifies the column of the referenceEntity to check for update.
    field_ptr createField(string name, IField::Type type,
        IEntity* const referenceEntity, string referenceField, string updateBy, bool notNull = false, int varcharSize = 0);

    //--------------------------------------------//
    //Begin a new transaction
    void beginTransaction();

    //--------------------------------------------//
    //Commit query to database
    bool commit();

    //--------------------------------------------//
    //Persists an entity instance to db
    void persist(IEntity * const entity);

    //--------------------------------------------//
    template <class T>
    vector<shared_ptr<T>> find(vector<WhereClause> restrictions)

    //--------------------------------------------//
    //Removes one or more entities given the specified conditions
    void remove(string const& entityName, vector<WhereClause> restrictions);
};

class WhereClause
{
public:
    typedef enum
    {
        EQUAL,
        NOT_EQUAL,
        GREATER_THAN,
        LESS_THAN,
        GREATER_THAN_OR_EQUAL,
        LESS_THAN_OR_EQUAL,
        BETWEEN,
        LIKE,
        IN_RANGE
    } Operator;

    string fieldName;
    string fieldValue;
    Operator op;
};

The PROs of this solution are reusability, high abstraction level and ease of changing DB engine.
The CONs is that it will be slower with respect to a direct solution
However I use it with sqlite on a db of a thousand of records with time of response in the range of 100 - 600 ms, which is acceptable to me.

In your case you will have something like:

class Egg: 
    public IEntity
{
public:
    Egg()
    {
        m_fields.push_back(shared_ptr<CField>(new CField("Id", IField::INTEGER, ...));
        // add fields
    }

private:
    fields_vec m_fields;
};

class Hen : 
    public IEntity
{
public:
    Hen()
    {
        m_fields.push_back(shared_ptr<CField>(new CField("Id", IField::INTEGER, ...));
        // add fields

        //here we add a field which represent a reference to an Egg record through the field 'Id' of Egg entity
        m_fields.push_back(shared_ptr<CField>(new CField("EggId", IField::INTEGER, dynamic_cast<IEntity*> (m_egg.get()), string("Id")));
    }

private:
    fields_vec m_fields;
    unique_ptr<Egg> m_egg;        
};

Then, you can get your Hen record, containing its Egg reference, from the EntityManager

vector<WhereClause> restrictions;
restrictions.push_back(WhereClause("Id", idToFind, EQUALS));

vector<shared_ptr<Hen>> vec = m_entityManager->find<Hen>(restrictions);

This example represents a 1:1 relation between Hen and Egg. For a 1:N relation you can invert the representation and put a reference of Hen in Egg

Simone Cifani
  • 784
  • 4
  • 14
  • If there is a short snippet that shows how to get the unique `egg` from an instance of `hen` (similar to the last part of R Sahu's), it would be easier to understand. :) – javaLover Apr 19 '17 at 07:59
  • What O(...) is the find? Is it worse than O(1)? – javaLover Apr 19 '17 at 09:32
  • The resulting query for the above example will be: SELECT Hen.Id, Hen.field1, ..., Hen.fieldN, Egg.Id, Egg.field1, ..., Egg.fieldN FROM Hen JOIN Egg ON Hen.EggId = Egg.Id WHERE Hen.Id = 'idToFind'; ---- Complexity may vary depending on relations between entities (number of JOINs), implementation and DB engine, as discussed in http://stackoverflow.com/questions/2065754/is-there-any-general-rule-on-sql-query-complexity-vs-performance – Simone Cifani Apr 19 '17 at 10:01
  • It seems to be slower than any of my poor solutions. :( – javaLover Apr 19 '17 at 11:04
  • Just one question...do you really need persistence on database or it is a my misunderstanding? – Simone Cifani Apr 19 '17 at 11:20
  • not sure about the word "persistence" ..... I want a long-term database. The one that will alive for the whole life-time of the game. I periodically add/remove relation in some time-steps. I usually want to query from that (quite global) database. – javaLover Apr 19 '17 at 11:22
  • 1
    Yes, that's what I mean. In my project, based on sqlite, I did a query of 1000 records of a complex entity with 6 references to other entities as a benchmark and it took 900 ms, which in my opinion is an acceptable time. However, most of this time is the time of response of the DB engine running the query. As I told before, this time is heavily dependent on database structure (more the relations between entities, greater the complexity of the queries, due to higher number of JOIN) and it is difficult to do an estimate. – Simone Cifani Apr 19 '17 at 11:52
  • That is an insightful information. Hm, 0.9 ms per query is quite a lot. Assume that "most of this time" mean 90%, it is still 0.1 ms. In real life, I have only 16 ms per time-step. (game 60 frame per second) I have to allocate the precious time to physic + graphic + game logic. – javaLover Apr 19 '17 at 12:14
  • In my project I preferred abstraction and reusability over speed, but you have strict real-time constraint and I agree that it doesn't fit to your needs. In fact, you should avoid continuous R/W operations on database and delegate them to a background thread or at least save the data at the end of the game session. – Simone Cifani Apr 19 '17 at 12:45
1

You could have each class contain a vector of strings that they accept upon creation of the class if the associations are known ahead of time. You could also add an update method that would update this container of names if more are discovered later. If the update function to update the list has been called and the class's container of names has been changed then the function also needs to have the class update itself with the proper class associations or relationships.

Each class would need these elements at minimum and for storing different types into a single container will require the use of a common non functional abstract base class with some purely virtual methods.

I'm using 2 classes that are derived from a common base interface for my example and the Primary class is named to represent the class that is having the relationship assigned to where the Associate class is the class is the delegated class that is being assigned to give the primary class that link of association.

class Base {
protected:
    std::vector<std::string> vRelationshipNames_;
    std::vector<std::shared_ptr<Base> vRelationships_;

public:
    Base(){}
    virtual ~Base(){}
    virtual void updateListOfNames( std::vector<std::string> newNames );
};

class Primary : Base {
private:
    std::string objectName_;
public:
    // Constructor if relationships are not known at time of instantiation.
    explicit Primary( const std::string& name );         
    // Constructor if some or all relationships are known. If more are discovered then the update function can be used.
    Primary( const std::string& name, std::vector<std::string> relationshipNames );

    // Add by const reference
    void add( const Base& obj );

    // Remove by const reference or by string name.
    void remove( const Base& obj );
    void remove( const std::string& name );

    // If needed you can even override the update method.
    virtual void updateListOfNames( std::vector<std::string> newNames ) override;
};

// Would basically have similar fields and methods as the class above, stripped them out for simplicity.
class Associate : Base {
    std::string objectName_;
};

We can then use a function template that takes two class objects to search to see if the Associate Object is in the list of names of the Primary Object

template <class T, class U>
T& setRelationshipBetweenClasses( class T& primaryObject, class U& associateObject ) {
    // Search through primaryObject's list of names to see if associate class is listed
    // If it is not then return from function otherwise, we need to search to see
    // if this class was already added to its list of shared pointers. 

    // If it is not found then add it by calling the Primary's add function

    // Then we also need to call the Associates add function as well by 
    // passing it a const reference to the Primary class this way both
    // classes now have that relationship. 

    // we also return back the reference of the changed Primary object.      
}

EDIT

The OP made a comment about using string and being slow; I used string here in the pseudo code just for clarity of understanding, you can replace the std::string with an unsigned int and just use a numeric ID. It will do the same and should be fairly efficient.

EDIT

For the OP -

A common interface of classes without definitions and implementations but their declarations might look something like this:

Example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

struct CommonProperties {
    std::string name_;
    unsigned int id_;

    // Default
    explicit CommonProperties() : name_(std::string()), id_(counter_) {
        counter_++;
    }
    // Passed In Name
    explicit CommonProperties(const std::string& name) : name_(name), id_(counter_) {
        counter_++;
    }

private:
    static unsigned int counter_;
};


class BaseObject {
protected:
    CommonProperties properties_;
                                                             // Sizes of Both Containers Should Always Match!
    std::vector<std::shared_ptr<BaseObject>> sharedObjects_; // Container of Shared Names
    std::vector<unsigned int> sharedObjectIDs_;              // Container of Shared IDs

public:
    explicit BaseObject(const std::string& strName) {
        properties_.name_ = strName;
    }

    // Virtual Interface for Abstract Base Class    
    virtual void add(const BaseObject& obj, const std::string& strName, const unsigned int id) = 0; // Purely Virtual Each Derived Class Must Implement
    virtual void update(const BaseObject& obj, const std::string& strName, const unsigned int id) = 0; // Also purely virtual
    virtual void remove(const std::string& strName) {} // Used string method to remove
    virtual void remove(const unsigned int id) {} // Use ID method to remove

    // Get Containers
    std::vector<std::shared_ptr<BaseObject>> getObjects() const { return sharedObjects_; }
    std::vector<unsigned int> getIDs() const { return sharedObjectIDs_; }
};


class Primary : public BaseObject {
// Member Variables
public:
protected:
private:

// Constructors, Destructor and Methods or Functions
public:
    explicit Primary(const std::string& strName) : BaseObject(strName) {
    }

    // Must Have Purely Virtual
    void add(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
        // Algorithm Here
    }

    void update(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
        // Algorithm Here
    }

    // other public methods;
protected:
private:
};

class Associate : public BaseObject {
    // Member Variables:
public:
protected:
private:

    // Constructors, Destructors and Methods or Functions
public:
    explicit Associate(const std::string& strName) : BaseObject(strName) {
    }

    // Must Have Purely Virtual
    void add(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
        // Algorithm Here
    }

    void update(const BaseObject& obj, const std::string& strName, const unsigned int id) override {
        // Algorithm Here
    }

protected:
private:

};    

#endif // EXAMPLE_H

Example.cpp

#include "stdafx.h"  // Used for common std containers and algorithms as well as OS and system file includes.
#include "Example.h"

unsigned int CommonProperties::counter_ = 0x00;

With this example I have both a string and ID. I do this for several reasons; if you ever need to write to a readable file, or to print to the screen or some other output device the contents or properties of this object I have string for human readability. The ability to search, remove and add by a string is available but for the sake of efficiency it should be done by the mechanics of the hidden engine that will instead automatically generate the IDs for you and use the ID system instead for faster searches, comparisons and removals.

For example let's say I generated 3 distinct objects that are of different classes: class1, class2, class3 their names and id's are set upon creation. I didn't show how to automatically generate a unique string with a base set of characters for a specific class and then append to that string a unique value each time that class is instantiated, but that is what I typically do if a name is not supplied. Supplying a name is optional and name generation is normally automatic. The table of the different classes and their properties name field would look like this:

// CLASS NAME  |   ID
    "class1"   |   0x01
    "class2"   |   0x02
    "class3"   |   0x03

Now what also makes this setup powerful is the fact that you can have multiple instances of the same class but each has their own unique name. Such as This

class PickupTruck {};

// Table of Names & IDS similar to above:
   "Chevy"     |  0x04
   "Dodge"     |  0x05
   "Ford"      |  0x06
   "GMC"       |  0x07

Now if you want to distinguish between the name of the actually class and the name of the actual object description; just make sure that you add a std::string as a protected member to the Base or Super class that these classes derive from. This way that name would represent the string representation of that class type, where the property sheet name would be the actual descriptive name of that object. But when doing the actual searches and removals from your containers, using ids for simple loops, counters and indexing are quite efficient.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • Thank. .... I am trying to analyse.... `Egg` and `Hen` are derived from Base, correct? Do I have to create 5 `Associate`-like class? Or just 5 instances of `Associate` class? .... What is the O() of query an `egg` from a `hen`? .... By the way, the performance is likely to be very low because of `string`. Actually, I expected that this would even be slower than my poor hash solution (2). – javaLover Apr 24 '17 at 08:47
  • You don't have to use string; you can instead us an unsigned int as an ID #. I used string here since it is more understandable. I'll edit and make a note of that in the original answer. – Francis Cugler Apr 24 '17 at 08:48
  • Oh, I understand. Thank for editing. To query an `egg` from a `hen`, do you use hash (slow) or linear-search (may be even more slow) , or something better? I guess you use linear-search at `vRelationships_`. – javaLover Apr 24 '17 at 08:51
  • It depends on the hierarchy on which search mechanism to use. Since these are vectors linear isn't too bad so as long as they are not overly large. Also by using vector you can also use array indexing too. Once you get this working you can replace a lot of it with newer features such as ranged loops, for each loops and the auto keywords. You can even use lambdas if needed to improve efficiency. – Francis Cugler Apr 24 '17 at 08:54
  • I don't use the `std's .at()` method unless if I'm using a `map`. Most the times vectors will handle most of your cases. Sometimes a `list` is needed and sometimes a `stack` or a `queue` and `dequeue` is needed. Most the times I don't really need a `hash` table. Now if you want you can look up the concept of a `Trie tree`, but that is beyond the scope of this and shouldn't be needed here. – Francis Cugler Apr 24 '17 at 08:58
  • It is quite a long (list) - at most 50 (shown in question), and I care about performance. .... A specific "search mechanism" is an essential part of solution. **1) list/queue** -> slow (similar to `R Sahu` & last solution from `Gruffalo`) ; **2) hash map** -> slow (similar to my 2nd & `Simone Cifani`'s) ; **3) tree** -> add / remove is `O(log n)`. It can be even slower than 2. .... Did you mentioned something better but I missed? .... I like when you mentioned about ID. – javaLover Apr 24 '17 at 09:09
  • Well if you are working with `std::vector` and you said your list is of only 50, you have to understand the inner mechanisms of how `std::vector` works. It also depends on how many times you update, change, modify, move the elements of the container within the container, move the container etc. that would cause performance to be an issue. Let's say initially you have 50 elements and the size upon creation is 100, you can easily add 50 more without having to do a resize and reallocation. Search is guaranteed to be linear. Using an `UINT` id makes the comparisons faster than `std::string`... – Francis Cugler Apr 25 '17 at 00:22
  • With `std::vector` you can use simple single for loops and quickly traverse the container with index notation instead using the function `container.at()` that causes overhead. – Francis Cugler Apr 25 '17 at 00:24
  • If I have time to work within my own compiler; I can add another update to the original of showing you a simple working code base of classes. – Francis Cugler Apr 25 '17 at 00:25
  • Oh, no need to make it compilable, just an essential part that is easy to understand is enough. :) – javaLover Apr 25 '17 at 01:47
  • "Search is guaranteed to be linear." It is the part that I frown upon. It can be 25 times (50÷2) slower than my first solution, isn't it? I can't usually afford that. Yes, it is also the disadvantage of some other solutions. – javaLover Apr 25 '17 at 01:54
  • Well if you use lists they are fast if they are sorted lists, but if you know where to search in a container say between bounds, then linear isn't that bad; even if you have 50k or 100k objects time is still in fractional parts of a second. Now if you have over a billion objects then linear is not good for you and you would want O(n) or as close as possible to O(n). – Francis Cugler Apr 25 '17 at 01:56
  • Oh, yes. With sorting, insertion/deletion/query is now `log(N)` ~ `log(50)base 2` ~ 5 (+ if-prediction). Yes, it is not so bad. In my personal feeling, I expect something more *architectural* rather than trying to pick the best data-structure. I know it might be not possible, it might be just my dream. .... About "fractional parts of a second", my situation is quite constrained - I have to run 60fps -> 16 ms for each frame for *everything* (physic+graphic+other every logic). – javaLover Apr 25 '17 at 02:02
  • Then what you need to do is have multiple little engines that are integrated into a single engine that is abstracted away from the basic game logic. Then what you can do is add in multithreading to have different threads and workers responsible for each component such as one for physics, one for graphics, one for audio processing, memory management etc. However, the hardest part with this is concurrency and keeping things synchronized. For example: your GPU time steps verses your drawing frame rate... – Francis Cugler Apr 25 '17 at 05:08
  • This next part is off topic but might give you some insight: loop up the concept of Batch Rendering while using shaders such as HLSL, GLSL etc. The concepts in here show you how to reduce the amount of rendering calls or batch processes that move across the bus. This way a desired maximum amount of information is sent from the CPU to the GPU a minimum amount of times thus getting rid of overhead. The first step is to get an algorithm that works for your needs that get's the job done and make it as modular as possible then... – Francis Cugler Apr 25 '17 at 05:11
  • once you have all that you need up and running and working and viably bug free then you move on to the next step to look for improvements. It is then at this point to be concerned with efficiency. – Francis Cugler Apr 25 '17 at 05:12
  • Yes, I agree with all of them in 3 comments. I tested multi-thread and it quite works (speed up 2-3 times). Then, I disabled it because it is too risky -> it becomes my low priority task. For GPU, Ogre3D 2.1 with AZDO-glsl manage it quite well already. I always create a dummy game + some test cases, and test/profile the whole thing. This is the origin of this question (hashmap take too much time). Oh, you has a web in your profile, nice. – javaLover Apr 25 '17 at 05:14