Search code examples
c++inheritancemultiple-inheritancediamond-problem

Diamond (multiple inheritance) with no data members


Suppose we have the usual diamond-pattern:

class A
{
public:
    virtual char foo() = 0;

    virtual ~A() {} 
};

class B :  public A
{
public:
    virtual char foo() { return 'B';}

    virtual ~B() {}
};

class C :  public A
{
public:
    virtual char foo() { return 'C';}

    virtual ~C() {}
};

class D :  public B,  public C
{
public:

    virtual char foo() { return 'D';}

    virtual ~D() {}
};

Please note that the base class A does not have any data members. It's in fact just an interface with pure virtual methods.

Now if i do:

D* d = new D();

A* a = static_cast<A*>(d);

the compiler will tell me that the cast is ambiguous as there are two A base classes.

But what if i do:

D* d = new D();

B* b = static_cast<B*>(d); // Or C* c = static_cast<C*>(d); 

A* a = static_cast<A*>(b);

Now i have a pointer to one of my A base classes and i can do a->foo().

Is this safe?

What i want to know is if i can do this double-upcasting to just have pointer to interfaces (with no data members) without the overhead of virtual inheritance. I don't plan to downcast the pointer anyway or do anything with it that isn't calling virtual methods.

I know this means having two base classes but as there are no members it shouldn't matter, or does it?

EDIT: I'm struggling to find a way to implement interfaces and i think i'll just have to use virtual inheritance.

Suppose i have a class Buffer (interface class), and a BufferImplementation class that derives from it.

Now suppose i have another interface IOBuffer (which derives from the other interface Buffer), with a IOBufferImplementation class that must derive both from BufferImplementation and the IOBuffer interface.

In my previous example, Buffer is A, BufferImplementation is B, IOBuffer is C, and IOBufferImplementation is D.


Solution

  • Is this safe?
    

    I think so. You are using one of the two paths to reach from the derived class D to Base A. You can now use the Interface Members safely.

    Just one note, if there are pure virtual methods in A that is not overridden in D, then using D->B->A path will always call the overridden method in B (similar for D->C->A path), though the implementation in C was intended/useful. This will also work when A has data members, and in this particular setup, the data members inherited via D->B->A will be used.