What are the typical problem patterns that benefit from a code
designed to make heavy use of multiple inheritance?
This is just one example but one I find invaluable to improve safety and mitigate temptations to apply cascading changes throughout either callers or subclasses.
Where I've found multiple inheritance incredibly useful even for the most abstract, stateless interfaces is the non-virtual interface idiom (NVI) in C++.
They're not even really abstract base classes so much as interfaces that have just a little bit of implementation to them to enforce the universal aspects of their contracts, as they're not really narrowing the generality of the contract so much as better enforcing it.
Simple example (some might check that a file handle passed in is open or something like that):
// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
// Pre: x should always be greater than or equal to zero.
void f(int x) /*final*/
{
// Make sure x is actually greater than or equal to zero
// to meet the necessary pre-conditions of this function.
assert(x >= 0);
// Call the overridden function in the subtype.
f_impl(x);
}
protected:
// Overridden by a boatload of subtypes which implement
// this non-virtual interface.
virtual void f_impl(int x) = 0;
};
In this case, maybe f
is called by a thousand places in the codebase, while f_impl
is overridden by a hundred subclasses.
It would be difficult to do this kind of safety check in all 1000 places that call f
or all 100 places that override f_impl
.
By just making this entry point to the functionality nonvirtual, it gives me one central place to perform this check. And this check is not reducing the abstraction in the slightest, as it is simply asserting a precondition required to call this function. In a sense, it's arguably strengthening the contract provided by the interface, and relieving the burden of checking the x
input to make sure it conforms to valid preconditions in all 100 places that override it.
It's something I wish every language had, and also wished, even in C++, that it was a little more of a native concept (ex: not requiring us to define a separate function to override).
This is extremely useful if you didn't do this assert
in advance, and realized you needed it later when some random places in the codebase were encountering negative values being passed to f
.