6

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.)

Soares
  • 73
  • 1
  • 6
  • "Where RectangleImpl inherits both SquareImpl and Rectangle to reuse, say, SquareImpl::area()" => inherit to be reused, not to reuse. – icecrime Nov 12 '10 at 15:20
  • dupe of http://stackoverflow.com/questions/249500/looking-for-a-better-way-than-virtual-inheritance-in-c ? – sean e Nov 12 '10 at 15:27
  • Is the non public inheritance of Rectangle in RectangleImpl intended? – daramarak Nov 12 '10 at 15:56

6 Answers6

4

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.

Dima
  • 38,860
  • 14
  • 75
  • 115
  • 1
    I used the shapes as an example of implementing something that are potentially complex behind a C++ interface class. While in this case `Square` is simple enough to have it's implementation and interface combined, that might not be the case for other situations. Anyway thanks for the note that Shape is a notoriously bad example. I guess I'll use FooBar in my examples next time. – Soares Nov 12 '10 at 20:57
  • Your suggestion to roll Square and SquareImpl into a single class works in this case, but Soares's problem occurs frequently when writing code for dependency injection. For DI, you will always have 2 SquareImpl classes that have vastly different implementations (one is real, one is a simple mock object). There's zero code to share, so it makes perfect sense to make them inherit from the same interface. FYI the solution I went with for DI is to just do virtual inheritance from the interface inheritance stack. A little duplication, but at least it works :/ – Weston Mar 27 '15 at 01:51
2

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

codymanix
  • 28,510
  • 21
  • 92
  • 151
1

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;

}
Community
  • 1
  • 1
Soares
  • 73
  • 1
  • 6
0

Square is not Rectangle, and Rectangle is not Square. Only thing they have in common is that they are Shapes. So:

class Square : public Shape {...};
class Rectangle : public Shape {...};

Their initialization functions are different, Square::setSide(double) and Rectangle::setLengthAndWidth(double, double). You don't need *Impl classes. Do your stuff in in Square and Rectangle.

Dialecticus
  • 16,400
  • 7
  • 43
  • 103
  • My example is just an illustration to the problem. I understand that there are better ways to implement shape but I just make up the classes to show my point of parallel inheritance. – Soares Nov 12 '10 at 15:25
  • 1
    Ah, bad illustration. Hmm, I think you should remove `public SquareImpl` from `class RectangleImpl`, and put the common code in some static functions that both `SquareImpl` and `RectangleImpl` can call. – Dialecticus Nov 12 '10 at 15:30
  • 1
    -1 I feel you didn't really respond to the askers problem, I think it is clear he gets the basics here – Elemental Nov 12 '10 at 15:30
  • He's trying to reuse SquareImpl::area() from RectangleImpl. That is impossible, and he clearly mixed something up. – Dialecticus Nov 12 '10 at 15:33
  • 1
    Buthe acknowledges that this is a weird (meaningless) thing to do here so he does understand the issues. – Elemental Nov 12 '10 at 15:39
0

I think you should be looking for virtual inheritance here so that you only have a single instance of shape underneath it all - this seems obvious from a semantic point of view - i.e. The shape that square and rectangleimpl is clearly the same shape.

Not really sure of the performance issue you mention - that doesn't seem an issue to me (in the sense that their is no extra overhead other than that of calling any v-function). I don't see why you can't access the setLength from square - difficult to see why you might be experiencing this and you don;t have source for the implementations.

Elemental
  • 7,365
  • 2
  • 28
  • 33
0

Your problem is Rectangle->Square->Shape knows nothing about the SquareImpl so it cannot use these functions to satisfy its abstract function requirements.

The easiest way is to not mix your interface and implementation inheritance when they are closely bound like this. Make RectangleImpl inherit from the Square and Rectangle interfaces. If SquareImpl and RectangleImpl are replicating too much of each other's code, use a class that does all this work and have as a member function in each implementation.

DanDan
  • 10,462
  • 8
  • 53
  • 69