Reputation: 391
I have an abstract type A
, and two derived types A1
and A2
.
I want to add a method M to class A, which takes a parameter of type A. But, I need ad hoc polymorphism.
Indeed, I need 3 implementations : A1::M(A1 a)
, A1::M(A2 a)
and A2::(A1 a)
, A2::M(A2 a)
.
But I want an abstract way to call the method M with pointers of type A.
I could put all signatures declaration in class A
, but it sucks.
Upvotes: 0
Views: 739
Reputation: 153899
This is double dispatch. When you write:
A* p1;
A* p2;
p1->M(*p2);
should dispatch both on the type of *p1
and the type of *p2
.
Before starting, you must realize that this means n^2
functions for
n
different derived types. And that somewhere, someone must be aware
of all of the derived types (unless you can define some sort of
"default" implemenation for an unknown pair of types).
There are two ways of implementing this. The simplest, if the hierarchy is closed (i.e. client code cannot introduce new derived classes) does use a host of virtual functions in the base class—generally protected, since they're not designed to be called outside the hierarchy:
// Forward references needed for all derived classes...
class A1;
class A2;
// ...
class A
{
protectd:
virtual void doM(A1* arg) = 0;
virtual void doM(A2* arg) = 0;
// ...
public:
virtual void M(A& arg) = 0;
};
In the derived classes, the implementation of M
is always the same:
void A1::M(A& arg)
{
arg.doM( this );
}
This is simple, and relatively efficient, but requires changes in the abstract base and all of the derived classes (which have to implement the new virtual function) each time you add a new derived class. It's useful for closed hierarchies, however; I've used it in classes using the strategy pattern for part of their behavior, where the various strategies were all defined in the source file, and not exposed to the clients (and the abstract base of the strategies was only forward declared in the header, so no header changes were necessary if I added a strategy).
A more general solution would involve an std::map
, with a pair of
typeid
as index. You can't use typeid
directly, since it's not
copyable. C++11 provides a type_index
to wrap it; if you're using an
older compiler, it's fairly trivial to implement one yourself. The
basic principle is something along the lines of (probably in A itself):
typedef std::pair<std::type_index, std::type_index> TypePairKey;
typedef void (*FuncPtr)( M* arg1, M* arg2 );
typedef std::unordered_map<TypePairKey, FuncPtr> DispatchMap;
static DispatchMap ourDispatchMap;
with:
void M( A& arg ) // NOT virtual !!!
{
DispatchMap::iterator entry
= ourDispatchMap.find(
DispatchMap::value_type( typeid( *this ), typeid( arg ) ) );
assert( entry != ourDispatchMap.end() );
// Or some default handling, maybe throw NotYetImplemented()
(*entry->second)( this, &arg );
}
The real problem comes with writing each of the individual functions and
inserting their addresses in the map (before first use). The functions
themselves, of course, can use dynamic_cast
, or even static_cast
, if
you can be sure that they will only be called from here, and they can be
friend(s) of the class(es) involved, but there are still
n2 of them. (One frequent solution is to make them
static members of one of the classes, and to have each derived class
define a static member of a type which does the registration of all of
the functions its responsible for.)
Upvotes: 0
Reputation: 272467
Use simulated double dispatch.
class A {
public:
virtual void M(A &) = 0;
virtual void M(A1 &) = 0;
virtual void M(A2 &) = 0;
};
class A1 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A1\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A1\n"; }
};
class A2 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A2\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A2\n"; }
};
(See e.g. http://ideone.com/nycls.)
I don't think there is a way of avoiding the multiple overloads in the base class.
Upvotes: 2
Reputation: 24846
If you want polymorphic behavior - then all you need is one method in Base class A. You can then reimplement that method in A1, A2.
After that you can write:
A *a1 = new A1();
A *a2 = new A2();
a1->M(a2); //polymorphic behavior
If you make something like this:
struct A
{
virtual void M(A *a) {}
};
struct A1 : public A
{
virtual void M(A1 *a) {cout << "A1" << endl;}
virtual void M(A *a) {cout << "A" << endl;}
};
Then:
A1 * a1 = new A1();
a1->M(a1); //prints "A1"
A * a = a1;
a->M(a1); //prints "A"
I don't think this the behavior you want
Upvotes: 0
Reputation: 4292
Why not do something like that?
void A1::M( A a )
{
if( dynamic_cast< A1* >( &a ) )
{
// do your A1::M1(A1 a) stuff
}
else
if( dynamic_cast< A2* >( &a ) )
{
// do your A1::M2(A2 a) stuff
}
else
{
throw std::logic_error( "Unsupported A type." );
}
}
And do equivalently for A2::M?
Upvotes: 0