2

I am working on a program language, and I came to the dilemma whether it should support virtual inheritance or not.

As a designer and implementer of the language, including that feature represents added complexity.

As a language agnostic software developer, it seems like the only benefit from including it is to facilitate and allow bad software design.

The cases I encountered where virtual inheritance is utilized all seem to be better candidates for "add-on" interfaces.

Of course, I do recognize that working with existing 3rd party code you don't have control over is definitely something that might mandate the support for virtual inheritance.

But for me this is not the case, I have a clean slate, and full freedom to define the paradigms and idioms of the programming language. Therefore the more elegant and efficient solution seems to be to simply eliminate the possibility that the need for virtual inheritance might arise and omit the feature altogether.

I also notice that it is a rather niche feature, of the several languages I have studied, C++ is the only one to support it. Leading me to presume it is not all that essential.

Naturally, I might also be overlooking something, which prompted me to probe the developer community for input on the subject.

EDIT: To clarify as requested, performance and memory efficiency are some of the primary goals of the language. Virtualism, dynamism and any high level programming functionality is optional and only included if necessary. It is not a language where everything is references and virtual calls.

Instead of

class WingedAnimal : extend Animal {}

my current plan is to avoid the need to address member duplication by means of

class WingedAnimal : require Animal {}

which essentially allows WingedAnimal to use Animal without inheriting it, guaranteeing that any users of WingedAnimal will be compatible.

  • I question the need for inheritance at all. Only rarely is it ever needed. An eye-opener is Perl/Tk which uses has_a relationships rather than the traditional is_a of most GUIs. For example, to put a button on a dialog box, you would say my $btn = $dlg->Button( %some_parameters ); No need to create a class MyDialogButton which inherits from the Button class. Ever button is an object of the Button class; it just their parameters that cause different behaviour. – shawnhcorey Jul 11 '18 at 12:06
  • C++ has it because, among other things, an object is the bytes that make up it's members and bases. In Python, members are all references, and all inheritance is "virtual". In Java, there is no multiple inheritance of fields, so this doesn't arise – Caleth Jul 11 '18 at 12:07
  • 1
    @shawnhcorey I'd say it has its benefits. There is the conceptual benefit of being able to say that a car is a vehicle than to say a car has a vehicle. There is the syntax advantage too, you can just car.start() rather than car.vehicle.start(). For buttons that is extreme overkill, I favor the solution where buttons use signals to implement the desired functionality rather than to subclass each and every button. A subclass is only justified if you change the class or add to it. What a button does can and should be external code, not part of the button. – dtech Jul 11 '18 at 12:10
  • 1
    Please consider the constraints why a C++ implementation may use virtual inheritance: when inheritance works by embedding the base object layout into the subclass layout, virtual inheritance can avoid embedding a common base multiple times in case of multiple inheritance. If your language doesn't support MI or does not use fixed object layouts (instead of, e.g., hash tables) then virtual inheritance is inapplicable. For an alternative consider MI in Python, or trait systems without data member inheritance. Please edit your question with more context about your language. – amon Jul 11 '18 at 12:18
  • 2
    @shawnhcorey I won't be hasty to dis anything, nothing is intrinsically good or bad. Even complexity can be good when the returns are worth it. – dtech Jul 11 '18 at 12:39
2

Virtual inheritance as used by some C++ implementations only makes sense under fairly specific constraints:

  • Classes have a fixed object layout that is known at compile-time.
  • An instance can be upcasted to a base class type. Therefore, inheritance must embed the base class layout into the subclass layout.
  • The language allows multiple inheritance.
  • In order to avoid duplicate embeddings with multiple inheritance, the position of the base class layout is not fixed but is available through additional pointer indirection.

Because of this indirection, virtual inheritance necessarily implies an unavoidable time overhead at runtime. Either your language will have to pay this overhead for all classes, or have to make a distinction between normal classes and MI-safe classes. MI with normal classes is safe as long as they don't lead to multiple embeddings, but this e.g. means that inheriting from another base class is not longer a backwards-compatible change.

The alternative approach is that class layouts are not fixed, but that MI-capable classes must only access instance fields through virtual methods/properties. The layout and these properties are then provided by the most-derived class that is actually instantiated. This is e.g. the approach that C# uses, where interfaces cannot declare fields but can declare (virtual) properties. This has a smaller performance impact than you might think due to to JIT compilation with extremely clever inlining. Similar approaches are used by some trait implementations, since traits cannot declare fields but can require and provide methods.

Most MI systems simply do not assume a fixed object layout but resolve methods and fields by name. E.g. this is fundamental to Python's MI approach. However, it seems that this might be undesirable for your language.

Any of these solutions imply some overhead. Virtual inheritance possibly implies the least overhead, but it has a strong impact on language ergonomics: classes have to explicitly choose between performance and MI-capability. The easiest way to solve this dilemma is to make a fundamental language design choice. Either decide to forbid implementation-MI, or decide to relinquish C/C++'s performance and “don't pay for what you don't use” ethos. I'd strongly consider the latter option because it has worked well in practice: most programs simply don't need every last bit of available performance.

MI and OOP implementations in general pose more difficult problems than just object layout (to which virtual inheritance is a possible solution). Additionally, you have to think about method dispatch strategies (e.g. compare C++-like multiple vtables per object, Java/C#-style single vtable + interface table search, Go/Rust-style fat pointers where the object doesn't have a vtable member, and hash table based dispatch as in Python). Consider also the problem of initialization order in a MI class. E.g. in Python all constructors in a MI hierarchy need to have the same signature which is extremely undesirable.

To be absolutely clear, this answer assumes that you do in fact want to support dynamic dispatch. Inheritance then doesn't just include the base class implementation like a mixin, but also allows dynamic dispatch through the base class interface. The MI difficulties are largely about maintaining a base-class compatible interface. If you get rid of that so that all method calls and field accesses go through the statically known subclass, then you don't need typical inheritance machinery like vtables or virtual inheritance – you are free to compute an arbitrary layout for each class.

  • Disabling MI altogether seems excessive, I'd rather just disable inheritance of already inherited objects. Dynamic dispatch is a must for a contemporary general purpose programming language, but again, I'd rather limit it to the cases where it is required rather than to make all calls virtual like some languages do. – dtech Jul 14 '18 at 15:49
  • @dtech Flat inheritance hierarchies certainly avoid difficult MI problems, but it's also a severe restriction. E.g. Rust tries to support “don't pay for what you don't use”. It has no class inheritance but interface inheritance. This mostly works fine, except that you can't express many classic OOP designs and patterns. In regard to the object model, Rust feels more like Haskell than C++. But Rust pairs these restrictions with a powerful zero-overhead generics system. Please consider carefully where you want to land on the usability/performance spectrum – to some degree these are exclusive. – amon Jul 14 '18 at 16:01
  • "overhead at runtime" over what, specifically? – curiousguy Nov 27 '18 at 23:19
  • @curiousguy In that part of my answer I'm concerned with the cost of accessing an object member (“field” or “attribute”). In C++, accessing a member is usually the same as accessing a struct member in C. That is: one pointer dereference, even for members in base classes. This changes for virtually inherited bases: to access them, you need at least one extra dereference to find the virtual base offset. Upcasting to a virtual base also requires one pointer dereference, for non-virtual bases this is a free operation. – amon Nov 27 '18 at 23:44
  • @amon ...and down-casting from a morally virtual base B of D can only be done with the much more complex dynamic_cast<D*> which navigates the hierarchy. Sample code for that is provided in the Itanium ABI documentation – curiousguy Nov 27 '18 at 23:50
0

In many ways, implementation inheritance of any kind is out-of-favour these days. The common ethos today is to prefer aggregation over inheritance. This suggests that a solution to problems of how to implement inheritance is to avoid the issue entirely: only allow interface inheritance (ie subtyping). Instead, provide useful tools to make aggregation easier: declarative delegation of unimplemented methods of an interface to a particular member, for example, could be a different way of achieving the same results that may be clearer and easier to understand.

  • What is an "interface"? – curiousguy Nov 27 '18 at 23:13
  • 1
    @curiousguy - hard to define exactly, but basically it's the public shape of an object, i.e. the set of members that may be used from externally. In interface-only inheritance, only methods/virtual member functions are typically allowed, not fields/member variables, because that makes the definition easier to work with. An example of a system that uses interface-only inheritance is Microsoft's COM, which may help understand what exactly an interface is. I believe golang also qualifies, although I've never used it so am not certain. – Jules Nov 28 '18 at 7:29
  • Typical C++ ABC (abstract base classes) often don't have many "fields". Virtual inheritance is especially useful for "interface" classes. – curiousguy Nov 28 '18 at 20:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.