composition patterns in C++, is there a preferred way?
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 m开发者_开发知识库ore..) 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?
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.
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
If A and B are declared in the same header file, the "forward" solution is the most straightforward IMO. Otherwise, use the "interface" solution.
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.
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.
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.
精彩评论