Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parallel Inheritance between Interface Classes and Implementation Classes in C++

I'm trying to use C++ abstract base class in the way similar with Java interface. Supposed that we have following interface classes with only pure virtual functions:

class Shape { virtual double area()=0; };
class Square : public Shape { virtual void setLength(double length)=0; };
class Rectangle : public Square { virtual void setWidth(double width)=0; };

and I try to implement Square and Rectangle the following way:

class SquareImpl : public Square { /*implementation*/ };
class RectangleImpl : public SquareImpl, Rectangle { /*implementation*/ };

Where RectangleImpl inherits both SquareImpl and Rectangle to reuse, say, SquareImpl::area(). However when I try to compile, two problems arise: Firstly, all methods in SquareImpl do not get inherited properly and I have to manually reimplement RectangleImpl::area() and RectangleImpl::setLength(). Secondly, this still introduces the diamond problem that Shape is ambiguous base of RectangleImpl.

I could compile the code if I virtually inherit Square from Shape, but I don't think the performance will scale with more derived interfaces added. Also strangely, RectangleImpl still doesn't inherit SquareImpl::setLength() although SquareImpl::area() is inherited well. (ignore the practicality here)

Another solution might be to make interfaces independent of each other, i.e. to make Square not inherited from Shape. But doing so will make me lose access to methods in Shape if I define functions that take a Square* pointer. It will also make static_cast impossible between Shape and Square.

So my question is, is there any other design pattern in C++ to solve this kind of parallel inheritance between interface classes and implementation classes, without requiring virtual inheritance?

(edit clarification: the example code above are just my dummy illustration on parallel inheritance between interfaces and implementations. I understand that there are better ways to implement shapes but my problem is not on how to implement shapes.)

like image 436
Soares Avatar asked Nov 12 '10 15:11

Soares


People also ask

Can a class both inherit and implement?

Yes of course. Have you gone through a basic Java tutorial, covering inheritance and interfaces? Classes don't inherit from interfaces, they just implement them. You can extend (inherit from) a single class, but you can implement multiple interfaces.

CAN interfaces allow multiple implementation inheritance?

The Java programming language supports multiple inheritance of type, which is the ability of a class to implement more than one interface. An object can have multiple types: the type of its own class and the types of all the interfaces that the class implements.

What are the differences between inheritance through classes and interfaces?

Inheritance is the mechanism in java by which one class is allowed to inherit the features of another class. Interface is the blueprint of the class. It specifies what a class must do and not how.

What is the relation between class and interface in terms of inheritance?

An Interface cannot inherit a class. A class can be inherited by another class using the keyword 'extends'. An Interface can be inherited by a class using the keyword 'implements' and it can be inherited by another interface using the keyword 'extends'. A class can contain constructors.


3 Answers

What you have here is the case of the Diamond Problem, which can happen in any OO language that allows multiple inheritance. This, by the way, is one reason why the designers of Java decided not to have multiple inheritance, and came up with the notion of an interface.

The way C++ deals with the diamond problem is Virtual Inheritance.

And, as codymanix pointed out, the square and the rectangle is a notoriously bad example for object oriented design, because as far as OO is concerned a square is not a rectangle.

Couple more points. First, The term for what you are doing here is multiple inheritance, not "parallel inheritance". Second, in this particular case it really makes little sense to have a class Square and a class SquareImpl. If you think you may have different implementations of Square, you should just have one base class which provides a default implementation and virtual functions that can be overridden by a derived class if necessary. In other words, you should roll Square and SquareImpl into one class with virtual functions.

You certainly can use an abstract C++ class like a Java interface, but most of the time there is no reason for it. Interfaces were added to Java precisely as a way to get around the lack of multiple inheritance. In C++ you can just go ahead and use multiple inheritance, although you should always do that very judiciously.

like image 108
Dima Avatar answered Nov 05 '22 10:11

Dima


You are by far not the first who met this problem. See A Square Is Not a Rectangle to give one example.

like image 27
codymanix Avatar answered Nov 05 '22 10:11

codymanix


After rethinking for a night and referring to the solution sean provided at Looking for a better way than virtual inheritance in C++, I came out with the following solution.

Here I redefine the problem to be more abstract to avoid the confusion we had on shapes. We have a Ball interface that can roll, a FooBall interface that contains Foo specific methods, and a FooBarBall interface that is also a FooBall and contains both Foo specific and Bar specific methods. Same as the original problem, we have a FooBall implementation and we wish to derive it to cover Bar specific methods as well. but inheriting both the interface and implementation will introduce diamond inheritance.

To solve the problem, instead of directly putting Foo and Bar specific methods into the derived Ball interfaces, I put a single method into a derived FooBall interface that converts the object into a Foo object through the toFoo() method. This way, implementations can mix in the independent Foo and Bar interface without introducing diamond inheritance.

Still, not all codes can be eliminated to derive all Bars from Foos freely. We have to still write independent implementations of Ball, FooBall, and FooBarBall that do not inherit from each others. But we can use the composite pattern to wrap the real Foo and Bar objects that are implemented differently. This way we can still eliminate quite a lot of code if we have a lot of implementations of Foo and Bar.

#include <stdio.h>

class Ball {
  public:
    // All balls can roll.
    virtual void roll() = 0;

    // Ball has many other methods that are not
    // covered here.

    virtual inline ~Ball() {
        printf("deleting Ball\n");
    };
};

class Foo {
  public:
    virtual void doFoo() = 0;

    // do some very complicated stuff.
    virtual void complexFoo() = 0;

    virtual inline ~Foo() {};
};

/** 
 * We assume that classes that implement Bar also
 * implement the Foo interface. The Bar interface
 * specification failed to enforce this constraint
 * by inheriting from Foo because it will introduce
 * diamond inheritance into the implementation.
 **/
class Bar {
  public:
    virtual void doBar() = 0;
    virtual void complicatedBar() = 0;

    virtual inline ~Bar() {};
};

class FooBall : public Ball {
  public:
    virtual Foo* toFoo() = 0;

    virtual inline ~FooBall() {};
};

/**
 * A BarBall is always also a FooBall and support
 * both Foo and Bar methods.
 **/
class FooBarBall : public FooBall {
  public:
    virtual Bar* toBar() = 0;

    virtual inline ~FooBarBall() {};
};


/* Composite Implementation */

class FooImpl_A : public Foo {
  public:
    virtual void doFoo() {
        printf("FooImpl_A::doFoo()\n");
    };

    virtual void complexFoo() {
        printf("FooImpl_A::complexFoo()\n");
    }

    virtual inline ~FooImpl_A() {
        printf("deleting FooImpl_A\n");
    }
};

class FooBarImpl_A : public FooImpl_A, public Bar {
  public:
    virtual void doBar() {
        printf("BarImpl_A::doBar()\n");
    }

    virtual void complicatedBar() {;
        printf("BarImpl_A::complicatedBar()\n");
    }

    virtual inline ~FooBarImpl_A() {
        printf("deleting FooBarImpl_A\n");
    }
};

/* Composite Pattern */
class FooBarBallContainer : public FooBarBall {
  public:

    /* FooBarBallImpl_A can take any class that
     * implements both the Foo and Bar interface, 
     * including classes that inherit FooBarImpl_A
     * and other different implementations.
     *
     * We'll assume that realFoo and realBar are
     * actually the same object as Foo methods have
     * side effect on Bar methods. If they are not
     * the same object, a third argument with false
     * value need to be supplied.
     */
    FooBarBallContainer( Foo* realFoo, Bar* realBar, bool sameObject=true ) : 
    _realFoo(realFoo), _realBar(realBar), _sameObject(sameObject) {}

    virtual void roll() {
        // roll makes use of FooBar methods
        _realBar->doBar();
        _realFoo->complexFoo();
    }

    virtual Foo* toFoo() {
        return _realFoo;
    }

    virtual Bar* toBar() {
        return _realBar;
    }

    virtual ~FooBarBallContainer() {
        delete _realFoo;

        // Check if realFoo and realBar are
        // not the same object to avoid deleting
        // it twice.
        if(!_sameObject) {
            delete _realBar;
        }
    }

  private:
    Foo* _realFoo;
    Bar* _realBar;
    bool _sameObject;
};


/* Monolithic Implmentation */

class FooBarBallImpl_B : public FooBarBall,
    public Foo, public Bar {

  public:
    virtual void roll() {
        complicatedBar();
        doFoo();
    }

    virtual Foo* toFoo() {
        return (Foo*) this;
    }

    virtual Bar* toBar() {
        return (Bar*) this;
    }

    virtual void doFoo() {
        printf("FooBarBallImpl_B::doFoo()\n");
    }

    virtual void complexFoo() {
        printf("FooBarBallImpl_B::complexFoo()\n");
    }

    virtual void doBar() {
        printf("FooBarBallImpl_B::doBar()\n");
    }

    virtual void complicatedBar() {
        printf("FooBarBallImpl_B::complicatedBar()\n");
    }

};

/* Example usage of FooBarBall */
void processFooBarBall(FooBarBall *ball) {

    Foo *foo = ball->toFoo();
    foo->doFoo();

    ball->roll();

    Bar *bar = ball->toBar();
    bar->complicatedBar();
}

main() {

    FooBarImpl_A *fooBar = new FooBarImpl_A();
    FooBarBall *container = new FooBarBallContainer(fooBar, fooBar);

    printf
    processFooBarBall(container);
    delete container;

    FooBarBallImpl_B *ball = new FooBarBallImpl_B();
    processFooBarBall(ball);

    // we can even wrap FooBarBallImpl_B into the container
    // but the behavior of roll() will become different
    container = new FooBarBallContainer(ball, ball);
    processFooBarBall(container);

    delete container;

}
like image 28
Soares Avatar answered Nov 05 '22 08:11

Soares