C++: Template Parameter Cyclic Dependency
This is more a best practice question than a language question in itself, since I already have a working solution to what seems to be a common stumbling block in C++.
I'm dealing with a typical cyclic dependency issue in template parameter substitutions. I have the following pair of classes:
template<class 开发者_高级运维X>
class A { /* ... */ };
template<class X>
class B { /* ... */ };
and I want to instantiate each one as the following:
// Pseudocode -- not valid C++.
A<B> a;
B<A> b;
that is, I want to 'bind' A to B, and B to A.
I can solve the problem, in a gross way, through a forward declaration with inheritance trick:
class sA;
class sB;
class sA : public A<sB> { /* ... */ };
class sB : public B<sA> { /* ... */ };
but this brings in a set of problems, since sA
and sB
are not indeed A
and B
. For example, I cannot invoke A
's constructors without properly duplicating them into sA
, or somehow sparkling casts around the code.
My question is: what is the best practical way to deal with this issue? Any specially clever solution to this problem?
I am using both MSVC2008 and G++, but solutions with compiler-specific extensions are welcome.
Thanks,
Alek
As mentioned, the best practical way to deal with this is to refactor - break the dependencies by decoupling.
Possible options include:
- interfaces by using virtual methods
- static interfaces (possibly using type traits or concept checks)
- using callbacks on one or both sides (possible eased via functors or signals)
This also helps you whenever your requirements suddenly change. Suppose you need a special server
in some cases - it should of course support all the clients you've already written, you don't want to rewrite them. Or you need some special client
s in some cases...
With your approach this would require rewriting both sides, whith a decoupled approach its just a matter of writing a modified version of the side you need to change.
Taking e.g. the static approach with clients:
template<class server>
class client {
server& srv;
public:
client(server& srv) : srv(srv) {};
void work(const request& req) {
srv.add_job(make_job(req));
}
};
Here, client
doesn't even need to know the concrete type of server
- if it doesn't have a member function add_job(job&)
or something compatible, compilation will simply fail.
If you want to get more formal, you could look into static assertions and concept checks.
Since a template's type names all its parameters, you can't have an endless loop of parameterization.
You are probably (certainly) just trying to send information in opposite directions at the same time. There's no problem with that, but you can't encapsulate the information in the classes that provide implementation.
template< class W > // define an abstract class to pass data
struct widget_traits {};
template<>
struct widget_traits< SpringySpring > { // specialize to put data in it
struct properties { … };
enum { boing = 3 };
};
template< class V >
struct veeblfetzer_traits {};
template<>
struct veeblfetzer_traits< VonNeumann > {
typedef int potrzebie;
};
template< struct WT, struct VT > // pass info by using as argument
struct MyWidget { … };
template< struct VT, struct WT > // both ways
struct MyVeeblfetzer { … };
精彩评论