1

Okay, let me start off by saying that this is probably highly subjective and argumentative, and probably doesn't belong on here (feel free to close if the feeling is mutual). Having said that, I'm looking at some code, and I want to come to a standard approach for composition as it seems that different people have different styles - so, here are the styles I've seen so far (there may be more..) The particular composition problem I looking at is where a class B owns an instance of class A, but, A needs to know that instance so that it can call methods of B.

#include <iostream>

using namespace std;

namespace forward
{
  class B;

  class A
  {
  public:
    A(B& b); 
  private:
    B& inst;
  };

  class B
  {
  public:
    B() : inst(*this) {}

    void foo() { cout << "forward::B::foo()" << endl; }

  private:
    A inst;
  };

  A::A(B& b) : inst(b) { inst.foo(); }
}

namespace interface
{
  struct IB
  {
    virtual void foo() = 0;
  };

  class A
  {
  public:
    A(IB* b) : inst(b) { inst->foo(); }
  private:
    IB* inst;
  };

  class B : public IB
  {
  public:
    B() : inst(this) {}

    virtual void foo() { cout << "interface::B::foo()" << endl; }

  private:
    A inst;
  };
}

namespace templated
{
  template <typename IB>
  class A
  {
  public:
    A(IB& b) : inst(b) { inst.foo(); }
  private:
    IB& inst;
  };

  class B
  {
  public:
    B() : inst(*this) {}

    void foo() { cout << "templated::B::foo()" << endl; }

  private:
    A<B> inst;
  };   
}

int main(void)
{
  forward::B b1;    
  interface::B b2;    
  templated::B b3;    

  return 0;
}

From this, I can see the following (not complete):

forward declarations Reduces need to include headers in headers, however you can't use the type that is forward declared in that header - i.e. the complete type has to be available when used.

interfaces Additional baggage (base class constructions, virtual function calls etc.)

templated I can't see any problems with this except compilation issues (i.e. ridiculous error messages etc.)

Now, I favour the templated approach - I think it's clean and has the advantage of being compile time enforced. So the crux of the question is, is there something technically wrong with this approach and if not, why would you take the other two approaches?


EDIT: I think the trivial example has not helped, in this particular instance, B is a resource manager, and own various components that are interlinked (A) - say for example various network connections etc. All the sub components can access each other through B - the whole system used to be a bunch of singletons... So the only reason that A knows of B is that it provides access to some other component that A needs...

It is interesting that most answers recommend forwarding, yet I still can't see why this is advantageous over the templated approach - is there some inherent fear of using templates in code other than for simple, generic functions?

Nim
  • 33,299
  • 2
  • 62
  • 101
  • It seems to me each of those styles adresses different issues, depending upon what is being asked of the design. – James Jan 21 '11 at 15:18

6 Answers6

3

Have you considered fixing the basic design issue so that you don't have a member that needs to know its parent in order to function? Without more information on the motivation for this arrangement, it's hard to recommend a better alternative, but the basic problem seems to be one of design, not of technique.

EDIT Sounds like you're looking for a Service Locator. Look over that, and see if it can't help your design issues.

Harper Shelby
  • 16,475
  • 2
  • 44
  • 51
  • I am trying to get away from having static singletons... I don't need a singleton registry in that sense, because the object that owns the resources is passed into each instance of the resources it owns, hence all resource can locate each other through this (akin to the service locator, without the nasty singleton) - and IMHO, much easier to unit test! – Nim Feb 21 '11 at 16:37
  • While I don't share the apparently common belief that 'singleton' is a dirty word, I can understand your position. If you won't change the design, then the forward declaration is really the idiomatic C++ way of approaching this problem. As for the 'limitation' of not being able to use the type in the header, that's okay - you should only have references or pointers in the header, and include the needed header in the implementation file. – Harper Shelby Feb 21 '11 at 22:55
1

I'd go for the first solution, it does exactly what you want.

I'd consider the other two solutions workaraounds, since:

  • interfaces is something not needed here, and, as you said, it adds complication and overhead.

  • templates, as above, is not needed, you're only using them as a hack, and it gives some issues (ridiculous error messages, all your implementation will need to be in your header file --and this could arise the same kind of problem with other classes--, ...)


there'd be even a fourth way to ease this problem: easing the declare before use rule

Community
  • 1
  • 1
peoro
  • 25,562
  • 20
  • 98
  • 150
  • Interesting, I've always thought that fowarding was a hack that the templates were a clean solution to! ;) – Nim Jan 24 '11 at 09:58
0

If A and B are declared in the same header file, the "forward" solution is the most straightforward IMO. Otherwise, use the "interface" solution.

Frederik Slijkerman
  • 6,471
  • 28
  • 39
  • They are not even in the same libraries! `B` is actually in a framework which is controlled by a set of traits and policy classes - and there are multiple products which use this framework each with their own traits and policies, but `B` owns the resources `A`. I guess that is the problem with trying to distill a problem in to a simple example... – Nim Jan 24 '11 at 10:02
0

That's not composition, that's just inheritance- that is, B inherits from A, effectively, and it just seems to me like you need to redesign your classes.

I personally use the forwarding solution, but of course, in my classes, B does not contain A.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • It's not inheritance, again, I tried to formulate a simple example and that has probably confused the issue, like I've said in the edit, `B` really does own `A` and there is no sense in inheriting from it (I am not a big fan of inheritance anyway - it's way too overused) – Nim Jan 24 '11 at 10:05
0

Circular dependencies like this can often indicate that the design needs to be revisted. Often a better solution is to introduce a third class C that mediates/interacts between A and B (or that A and B interact with).

If however A and B are indeed extremely tightly coupled, use forward declarations like in your first example and put both classes in the same header.

EDIT (for OP's edit): In this case it sounds like you need an "algorithm" class that knows about the manager AND all the subobjects and delegates each piece of work to the appropriate contained object (getting it from the container as needed). It can pass for example a reference to a B into the methods of A that need it rather than introducing the circular dependency.

I think people prefer forward declarations because everyone knows how they work and understand the pattern in question. In the template example it might take longer for someone to realize that the only reason the template exists is instead of using a forward declaration.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • `B` only owns `A` (and others like `A`), my trivial example did not help get this across, think of `B` as a resource manager where `A` are the resources, however `A` needs access to other resources (owned by `B`) to function - i.e. `B` is effectively what your `C` is! (argh. too many `ABC`s for monday morning! :( ) – Nim Jan 24 '11 at 10:01
0

There are cases where callbacks are elegant solutions (event-handlers, timers) where in java you would normally use nested classes.

I would consider forward declarations if the owned class is (or could functionally be) a nested class. What else would A call then B?

interface would be used when it is reasonable to assume that different callbacks are used, but only 1 per class (less likely).

Otherwise, I would use a signaling mechanism as callback (observer)

Frankly, I don't see what templated buys you, maybe because I haven't used it yet.

stefaanv
  • 14,072
  • 2
  • 31
  • 53
  • templates are IMHO an elegant solution to the hack that is forward declarations.. The error messages are scary, but once you get used to the quirks of your compiler, it doesn't take much effort to decode (again just an opinion)... – Nim Jan 24 '11 at 10:10