views:

152

answers:

5

The facts:

  • I have two predominant classes: Manager and Specialist.
  • There are several different types of Specialists.
  • Specialists often require the help of other Specialists in order to get their job done.
  • The Manager knows all of the Specialists, and initially each Specialist knows only their Manager. (This is the problem.)
  • At runtime, the Manager creates and stores a list of Specialists. Then the Manager iterates through the list and asks each Specialist to initialize. During their initialization, each Specialist asks the Manager to supply them with other Specialists that fulfill some description. Once this is complete, the Manager then goes into a loop during which the Specialists are asked sequentially to perform their specialized task.

To me it seems that this is a decent pattern, but since a Manager has a list of Specialists and a Specialist has a Manager I'm getting circular dependency problems.

Is this a case where I should somehow forward declare the existence of one class from another? (If so, how?) Or should I use some design pattern to fix this problem? (If so what?) Also... I though the pattern itself was pretty o.k. so I wouldn't mind someone helping me understand why this is a bad thing.

+1  A: 

One option is to forward declare one of the people, as you suggest:

struct specialist;

struct manager
{
    std::vector<std::shared_ptr<specialist> > subordinates_;
};

struct specialist
{
    std::weak_ptr<manager> boss_;
};

However, if you end up having more of a tree-structure (where you have multiple layers of management, a person base class would also work:

struct person
{
    virtual ~person() { }
    std::weak_ptr<person> boss_;
    std::vector<std::shared_ptr<person> > subordinates_;
};

You can then derive specific classes for different types of people in the hierarchy. Whether or not you need this depends on how exactly you intend to use the classes.

If your implementation doesn't support std::shared_ptr, it may support std::tr1::shared_ptr or you can use boost::shared_ptr.

James McNellis
@James - in this model do you have to ensure that all `manager` pointers are wrapped in `shared_ptr`? Otherwise there's no way to validate the `weak_ptr` is there? Curious about this as it's coming up a lot for me.
Steve Townsend
@Steve: Yes. It's a lot easier if you only have one class type in the hierarchy (like `person` in my second example).
James McNellis
+1  A: 

this is normal stuff. You just need

class Manager;

in the specialist header and

class Specialist; 

in the manager header

if you are using shared_ptrs you might find shared_from_this useful. (Not for looping but because it sounds like you will need it anyway)

pm100
+10  A: 

In both cases, forward declare the other class:

Manager.h

class Specialist;

class Manager
{
    std::list<Specialist*> m_specialists;
};

Specialist.h

class Manager;

class Specialist
{
    Manager* m_myManager;
};

The only time you need to bring in the header file for a class is when you need to use a member function or variable within that class, or need to use the class as a value type etc. When you only need a pointer or reference to a class, a forward declaration will suffice.

Note that forward declarations aren't just for solving circular dependencies. You should use forward declarations wherever possible. They are always preferable to including an extra header file if it is at all viable.

Peter Alexander
+1 - "forward declaration" are the key words
leonbloy
"They are always preferable" I _strongly_ disagree and would argue that they are _rarely_ preferable wherever they can be avoided. Using forward declarations in a large makes for harder to understand code because it becomes harder to track down dependencies. Further, there is often little or no performance gain when using a modern compiler that caches files aggressively and supports precompiled headers. [On an unrelated note, why do you suggest `std::list`?]
James McNellis
Track down what dependencies? If a forward declare works then there is no longer a dependency. Also, it's just as easy to follow a class trail as a header trail in any halfway decent IDE. As for using `std::list`, he says in the OP that the Manager stores a list of specialists, so I decided to interpret that quite literally. I would probably use a `vector` myself, but of course it depends on the particular use cases.
Peter Alexander
@Peter Thanks! I was afraid that I was entering some kind of nonintuitive anti-pattern. Good to see that everyone thinks this is pretty standard fare.
John Berryman
+2  A: 

It's a matter of taste, but forward declaration often is a good alternative to includes in header files even without circular dependencies. (I don't want to raise a discussion on that in this place.) So, here is an example on how to apply forward declarations for your problem:

In Manager.h:

// Forward declaration:
class Specialist;

// Class declaration:
class Manager
{
    // Manager declarations go here.
    // Only pointers or references to
    // the Specialist class are used.
};

In Manager.cpp:

#include "Specialist.h"

// Manager definitions/implementations
// using the Specialist class go here.
// Full Specialist functionality can be used.

In Specialist.h:

// Forward declaration:
class Manager;

// Class declaration:
class Specialist
{
    // Specialist declarations go here.
    // Only pointers or references to
    // the Manager class are used.
};

In Specialist.cpp:

#include "Manager.h"

// Specialist definitions/implementations
// using the Manager class go here.
// Full Manager functionality can be used.
Flinsch
+1  A: 

While everyone else is answering the core question I thought I'd point this out.

At runtime, the Manager creates and stores a list of Specialists. Then the Manager iterates through the list and asks each Specialist to initialize. During their initialization, each Specialist asks the Manager to supply them with other Specialists that fulfill some description. Once this is complete, the Manager then goes into a loop during which the Specialists are asked sequentially to perform their specialized task.

I just want to point out that this needs to be a two-step process. How can the manager tell specialist 1 what specialists exist for task B if the manager only knows about one specialist so far? So you need:

1) manager goes through list of specialists and asks them to identify themselves.

2) manager goes through list of specialists and asks them what specialties they need access to, telling them who can fulfill their requirements.

3) manager goes though list of specialists and tells them to perform their actions.

jmucchiello