问题
I'm having difficulty finding the (what I'm sure is a very common) design pattern to work around the following problem. Consider this piece of code:
class AA {};
class BB : public AA {};
class A
{
public:
virtual void foo(AA& aa) = 0;
};
class B : A
{
public:
void foo(BB& bb){cout<<"B::foo"<<endl;}
};
int main()
{
B b;
BB bb;
b.foo(bb);
}
This code will not compile because the class B does not override the pure virtual function 'foo'. The compiler considers the foo that B declares only as an overload to foo because co-variance is not allowed in input parameters in overriden functions.
Now, I understand the reason for this. The fact that B inherits from A means that it should be able to handle any calls to foo with parameters of type AA, and the previous code gave no implementation to handle any parameter type except BB.
Of course I could just cast the aa to BB in B's foo implementation, but I'm looking for a solution that preserves type-safety and actually forces the implementer of B class to also implement a class that inherits from AA in order for the code to compile. In an ideal world I would be able to write something that looks like this pseudo-code:
class A
{
public:
abstract class AA{}; //indicates that any child of A must implement also child of AA
abstract void foo(AA& aa);
};
class B : public A
{
public:
class BB : AA{}; //will not compile without this
void foo(BB& bb){cout<<"B::foo"<<endl;}
};
Is there a way to achieve something similar to this in C++? (boost maybe, by some kind of mapping object without the need for inheritance)
Please note that in actuality (unlike in the example), inheritance between BB and AA is crucial as AA has many children that share many qualities, and in the end what I want to accomplish is to iterate over a vector of A classes and run 'foo' only with appropriate parameters (vector of AA's)
回答1:
To provide type safety, you should use templates instead of inheritance.
class AA {};
class BB : AA {};
template <typename Managed> class FooManager {
virtual void foo(Managed& m) { /* default implementation */ }
};
class B : public FooManager<BB> {
void foo(BB bb) { cout << "B:foo()" << endl; }
};
Later in the code, for example, if you want to traverse an array,
template<typename Managed> void processAll(vector<Managed> v, FooManager<Managed> mgr) {
for(Managed& m : v) mgr.foo(m);
}
B b;
vector<BB> bbs;
processAll(bbs, b);
Edit: typo fix
回答2:
you could look at the visitor pattern. this is the general solution to 'double dispatch' type problems (dispatch to a virtual function based on the object and the message).
That is, put foo()
into a visitor, and rename A::foo()
to A::Visit(FooVisitor& )
:
edit: to clarify, it might help to disentangle the purposes of your hierarchies. If you think about it, you are trying to model the relationship of one hierarchy (AA
and BB
) in terms of another (A
and B
). This is pretty awkward to model, or even to think about conceptually.
To remodel this as a visitor, you'd generally turn one of the hierarchies into a single class, and instead model the operations you can perform on that class in a hierarchy of algorithms via a visitor. This is more robust because it forces you to explicitly implement each combination of hierarchy relationships, and will break at compile-time (good) if you modify the hierarchy later on.
class A; class B;
struct AVisitor
{
virtual ~AVisitor() { }
virtual void Visit(A& ) = 0;
virtual void Visit(B& ) = 0;
};
class A
{
public:
virtual ~A() { }
virtual void Visit(AVisitor & visitor) { visitor.Visit(*this); }
};
class B : public A
{
public:
virtual void Visit(AVisitor & visitor) { visitor.Visit(*this); }
};
struct PrintingVisitor : public AVisitor
{
void Visit(A& a){cout<<"A::foo"<<endl;}
void Visit(B& b){cout<<"B::foo"<<endl;}
};
int main()
{
B b;
PrintingVisitor printer;
b.Visit(printer);
}
来源:https://stackoverflow.com/questions/7144893/override-contra-variance-workaround-needed