Use of multiple-inheritance for ‘Interface’ semantics

After several years of C++ I feel the language is extremely powerful for solving technical engineering problems, but doesn’t really welcome more general OO patterns, which are desperately needed in order to structure and maintain a very large code base, UI, knowledge base and associated AI. In search for patterns that help reduce clutter and verbosity (templates and meta-programming already helped), I’m pondering the extended use of Interfaces. So my question is:

According to the decades long experience of some Juce users here, is it good practice in C++ to make use of the ‘Interface’ pattern a lot?

Printable, Sortable, Transposable, Editable, Dragable, etc. come to mind. While interfaces nicely enforce a consistent naming and semantics across class trees (which is a huge plus in itself), I wonder if going down that path will really lead to more expressive and lean code in the long term.

Seeing it rarely used in the Juce library, what are the downsides?

Inheritance isn’t a bad thing but in my experience prefer composition over inheritance. Also I’m not a huge fan of pure virtual interfaces. I’ve seen cases of pure virtual interfaces and bases classes where your encouraged to derive from the base class (rather than the interface) in which case I have to ask why not have the base implementation in the interface in the first place? Some things make sense to be pure virtual of course and there will always be exceptions (Listener classes come to mind). IME when interfaces are over used your hands tend to be tied in what can and can’t be done or your forced to override methods that are unnecessary. I recall a project where I had a lot of classes to implement in which I was required to inherit certain interfaces in order to do what seemed like trivial things, I added overrides with asserts in each override so when they were hit I could implement them as required, I hit maybe 20% of them and the remaining 80% stayed as asserts and default return statements.

1 Like

I think this sort of pattern will naturally emerge more regularly when concepts become more ubiquitous in the language.

1 Like

One I found quickly is that you can’t really expect multiple “interface” types at once. For example, if you have a method (or constructor) that expects a parameter to be both Printable and Editable. In Java, you could write T extends Printable & Editable, but in C++ that syntax doesn’t exist, so you have to choose one. You could template the parameter type, and static_assert that both Printable and Editable is_base_of the parameter type, but that brings its own disadvantages and is harder to read.

With concepts though, you can specify multiple.

1 Like

seems like java has a shorthand way of declaring a type that derives from 2 bases?
c++ would (obviously) just do

struct Base1 { };
struct Base2 { };
struct twiceDerived : Base1, Base2
{

};

void someFunc( twiceDerived& td );

I’ll re-read the original post, but i don’t see what’s so wrong with this pattern…
sidebar, can we get the :thonk: emoji?

That’s a good thought :slight_smile: but then classes deriving Base1 and Base2 have to be changed to derive twiceDerived instead. Some of those may be in 3rd party code. That code has to know about twiceDerived, and has to be modified which may not be possible.

And there’s this case:

class Printable {…};
class Editable {…};

class Document : public Editable {…};
class PrintableDocument : public Document, public Printable {…};

Here, if you change PrintableDocument to derive from twiceDerived, it inherits Editable twice (diamond problem).
Also you need a combinatorial amount of “combination classes” like twiceDerived, which may or may not be a problem.

look up virtual inheritance.

Thanks to all. Exactly the kind of responses I was hoping for.

In dynamic languages, patterns are mostly a matter of organization and style. With statically typed languages you sometimes hit a wall at some point along the way. It is difficult to estimate in advance where a particular pattern will take you.

IMHO so far, an interface only makes sense, if

  1. Pure virtual functions are avoided in favor of defaulting to some sensible NOP

  2. It implements some significant code on its own

  3. It is used across class trees that have no common base class

  4. It provides useful templates, types, static helpers, etc. related to its purpose

Because actually the whole point is to make (2) re-usable across (3) in pursuit of the DRY principle.

1 Like

I think your issue could be fixed with virtual inheritance.
More details here: Interface implementations and multiple inheritance in C++

Well, whatever you are describing isn’t an interface, so that’s rather confusing. In normal OO concepts, an interface has no functionality of it’s own, so the C++ equivalent is a class with only pure virtual methods, so given your (1) and (2) what I think you are describing is just a base class.

Now when you are defining a class hierarchy there are many ways of going wrong, and in general, coming up with hard and fast rules tends to cause trouble - it’s much better to have guiding principals but to know when to break them.

2 Likes

Thanks @DEADBEEF in the meanwhile I collected more experience using C++, which also led me to virtual inheritance.

My main grief still is that C++ isn’t actually supporting the OO patterns I regularly use to tame big projects, where class trees can go 10+ levels deep. Strict static typing puts an inconvenient limit on writing generic code. So I end up with heavy use of templates, which is cool, but essentially duplicates the same code over and over, only to make simple generic algorithms work with a wide range of objects. Hence my desire to leverage multiple inheritance to the max.

@cesare Yes it’s a lean base class, not an interface in its original sense. And yes, there are many ways of doing a class hierarchy wrong. Only that in C++ the re-doing of a failed attempt is more painful than, for example Smalltalk, where a class tree can be completely reorganized within minutes, mostly by drag & drop.

Allowing multiple inheritence makes the rules about function overloads and virtual dispatch decidedly more tricky, as well as the language implementation around object layouts. These impact language designers/implementors quite a bit, and raise the already high bar to get a language done, stable and adopted.

It is simple to think this way, if class A inherits from multiple classes, then the class A will have the same grandparent class multiple times, this means the code will be complicated and a series of bugs will go unacknowledged. Personally, I think multiple inheritance has a bad rap, and that a well done system of trait style composition would be really powerful/useful… but there are a lot of ways that it can be implemented badly, and a lot of reasons it’s not a good idea in a language like C++.