Enjolras
Enjolras

Reputation: 391

Inheritance and polymorphism using abstract class

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

Answers (4)

James Kanze
James Kanze

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

Oliver Charlesworth
Oliver Charlesworth

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

Andrew
Andrew

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

kostrykin
kostrykin

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

Related Questions