1

Imagine having multiple classes deriving from one base class. All classes need to know each other, which is not an option, since it's a bit larger project I'm working at. I'm going to use a made-up inventory - item relation as an example due to it's simplicity.

class Inventory : public Base {
    std::vector<Base*> m_items; // These CAN be Items
};

class Item : public Base {
    Base* m_parent; // This CAN be Inventory
};

These two classes are obviously in different files and they will need to use each others methods, which their base class doesn't have. Notice the word CAN, not MUST, meaning that the m_parent and m_items can be objects of any class derived from Base. So Item's parent could be either Inventory or TreasureChest.

tl;dr Two classes must be able to communicate with each other, without knowing each others' type. How, and what, would be the best way to implement such an activity?

brianpeiris
  • 10,735
  • 1
  • 31
  • 44
  • 3
    Take a look here? Might help: http://stackoverflow.com/questions/4964482/how-to-create-two-classes-in-c-which-use-each-other-as-data – Rivasa Nov 26 '12 at 22:39
  • How would I use forward declaration in my case? I would anyways have to include `Inventory.h` in `Items.cpp` and/or the other way around, and as mentioned, I don't know for sure that the parents gonna be Inventory - it could be any other `Base` object too. –  Nov 26 '12 at 22:44
  • Could I use Model-View somehow? No? Just a thought. –  Nov 26 '12 at 22:47
  • Let's assume that your `Item` object determines it needs to interact in some way with the `Base*` object. Does the item have _a priori_ knowledge of the type of `Base*` object it is holding? – Chad Nov 26 '12 at 22:48
  • I'm afraid I'm from Finland and I have no clue what a priori means, wikipedia didn't clarify much either. Can you ask it in an other way? –  Nov 26 '12 at 22:57
  • @Mahi If it knows right from the outset. If, when the `Item` object is instantiated, it knows which type that `Base*` is. (Or, it *could* know.) – Ben Richards Nov 26 '12 at 22:59
  • This is more a Linguistics.SE comment, but *a priori* is Latin and used in philosophy, but it obviously can be applied elsewhere. – Ben Richards Nov 26 '12 at 23:00
  • Still not sure if I got it right, but yes, Item could know the type of it's parent when it (Item) is constructed. –  Nov 26 '12 at 23:03

3 Answers3

1

One way would be to define an abstract function for the type of communication in the Base class. Then implement this function in your derived classes. This allows you to handle the kind of communication for each type you need.

But with two way reference you have to be more cautions about deleting such objects. This type of architecture is very error prone.

For two way references with communication could look like: base.h:

class Base {
  void doCommunication(Base *caller) = 0;
};

Inventory.h:

class Inventory;      // forward declaration for Item class
#include "Item.h"

class Inventory : public Base {
   void doCommunication(Base *commCaller) { 
      // check type
      Inventory *invCaller = dynamic_class<Inventory*> (commCaller);
      if(invCaller != nullptr) {
         // called from inventory and you are able to use inventory

         return;  // you can stop here cause commCaller can only be Base class instead but not Item
      }

      Item *itemCaller = dynamic_class<Inventory*> (commCaller);
      if(invCaller != nullptr) {
         // called from item and you are able to use item

         return;  // you can stop here cause commCaller can only be Base class instead but not inventory
      }
   }
};

the Item.h looks pretty similar to inventory class, the doCommunications has to be overided for the item specific functionalities.

i cant test the code yet, but it should work. Cause of dynamic_cast you are able to cast to the destination object you need and call the needed functions. If it fails you get an nullptr.

Hope it helps.

cheers Lukas

Lukas
  • 52
  • 2
  • Yeah I thought about this earlier, found it quite dangerous and I'd rather not use it, if there are better ways. –  Nov 26 '12 at 22:47
  • 5
    one possibility would be to rethink your architecture like: does an item really has to know in which inventory it is stored. If no you would have an one way reference and live would be less complicated :D – Lukas Nov 26 '12 at 22:49
  • Unfortunately, it has to be a two way reference. –  Nov 26 '12 at 22:55
  • I'd second that, though, but if an item needed to retain information about the inventory it's in, you could store that information within the Item class itself, right? And have whatever code that handles the Item objects modify this information if any of this information changes, correspondingly. – Ben Richards Nov 26 '12 at 22:57
  • Item must be able to call methods from it's parent, and Inventory must be able to call methods from it's children. If item only knew some information of it's parent, but not the parent itself, this wouldn't be possible. –  Nov 26 '12 at 23:04
  • That sure does it's job, but its not what I'm looking for. Inventory doesn't know wether his m_items will construct of Item*s or Potion*s, it only knows that what ever object's it's going to contain, they derive from Base. –  Nov 26 '12 at 23:18
  • you can check each item with dynamic_cast too. The overhead is not to big and it would look like: Potion *potion = dynamic_cast (m_items[0]); if potion is not a nullptr the first element in m_items is a potion and you are able to call his functions like: "potion->doPotionStuff();". But here potions has also be derived from the base class. – Lukas Nov 26 '12 at 23:26
  • So what, I'm gonna include 20 files and just do dynamic_cast for each type until I find the matching one? What about Item.h, if I want ro use parent different from Inventory? I include 20 files there too? And 20 includes in Potion.h ? –  Nov 27 '12 at 05:41
1

In your example there isn't any actual problem: There is no immediate dependency between the two classes and you didn't show any part of the implementation. In case the implementation actually needs to access specific Inventory and Item method from the respective other class, the way to go is to factor each set of operations into a respective base class, possibly also deriving from Base and providing suitable functions.

For example

// base.h
class Base {
public:
    virtual ~Base();
};

// abstractitem.h
class AbstractItem: public Base {
public:
    virtual int weight() const = 0;
};

// abstractinventory.h
class AbstractInventory: public Base {
public:
    virtual int totalWeight() const = 0;
};

// item.h
class Item: public AbstractItem {
public:
    int weight() const;
    // uses of AbstractInventory
};

// inventory.h
class Inventory: public AbstractInventory {
    void addItem(AbstractItem const* item);
    int totalWeight() const;
    // uses of AbstractItem
};

Note that the abstract and independent operations of inventory and item are just factored out. There isn't any dependency between the interface or the implementation although the concrete objects will actually be able to call each other.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
0

Two classes can "know each other" just fine. Just only forward declare referenced class in the header file of its user, and only include the referenced class header in the cpp (not the h)...

A.h:

struct B; // fwd decl

struct A
{
    B* b;

    void fa();
};

A.cpp:

#include "A.h"
#include "B.h"

void A::fa() { b->fb(); }

B.h:

struct A; // fwd decl

struct B
{
    A* a;

    void fb();
};

B.cpp:

#include "B.h"
#include "A.h"

void B::fb() { a->fa(); }

This is obviously an infinite runtime loop, but you get the point.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • As I mentioned, I don't know wether it's going to be type A, B or C, I need to globalize it for any object deriving from `Base`. Also, I can not simply let two files know each other, thats some real bad coding. What if I now wanted to use Item class with parent of type `TreasureChest` instead of `Inventory`? I would include the whole `Inventory.h` without ever using it. Soon my including paths would be mazes and including one file would drag 10 unused files with it. Or if I wanted to use Item class alone in one of my other projects, again without Item? –  Nov 27 '12 at 05:33
  • Your problem statement is not clear. In my example above you understand that there is no compile-time dependency between a user of class A and B. That is if I include A.h it will not also include B.h, and I can use class A without "knowing about" class B. – Andrew Tomazos Nov 27 '12 at 06:17
  • You didn't seem to understand the question. A doesn't know wether it's parent will be B, C, D or E. I can't include all of those classes. –  Nov 27 '12 at 07:42
  • I think you need to edit your question and elaborate your object model more. What is an example of a "communication" between Base, Item, Inventory and/or TreasureChest? What is the base class of TreasureChest? Provide a couple more examples (if any) of subclasses of Base, Item, Inventory and TreasureChest. – Andrew Tomazos Nov 27 '12 at 08:03