What is the exact problem with multiple inheritance?
Asked Answered
A

11

132

I can see people asking all the time whether multiple inheritance should be included into the next version of C# or Java. C++ folks, who are fortunate enough to have this ability, say that this is like giving someone a rope to eventually hang themselves.

What’s the matter with multiple inheritance? Are there any concrete samples?

An answered 22/10, 2008 at 14:17 Comment(4)
I would just mention that C++ is great for giving you enough rope to hang yourself.Scarification
For an alternative to multiple inheritance that addresses (and, IMHO solves) many of the same problems, look at Traits (iam.unibe.ch/~scg/Research/Traits)Mcfarland
I thought C++ gives you enough rope to shoot yourself in the foot.Olia
This question seems to assume that there is a problem with MI in general, whereas I've found lots of languages where MI is in casual use. There are certainly problems with certain languages' handling of MI, but I'm not aware that MI in general has significant problems.Parterre
R
97

The most obvious problem is with function overriding.

Let's say have two classes A and B, both of which define a method doSomething. Now you define a third class C, which inherits from both A and B, but you don't override the doSomething method.

When the compiler seed this code...

C c = new C();
c.doSomething();

...which implementation of the method should it use? Without any further clarification, it's impossible for the compiler to resolve the ambiguity.

Besides overriding, the other big problem with multiple inheritance is the layout of the physical objects in memory.

Languages like C++ and Java and C# create a fixed address-based layout for each type of object. Something like this:

class A:
    at offset 0 ... "abc" ... 4 byte int field
    at offset 4 ... "xyz" ... 8 byte double field
    at offset 12 ... "speak" ... 4 byte function pointer

class B:
    at offset 0 ... "foo" ... 2 byte short field
    at offset 2 ... 2 bytes of alignment padding
    at offset 4 ... "bar" ... 4 byte array pointer
    at offset 8 ... "baz" ... 4 byte function pointer

When the compiler generates machine code (or bytecode), it uses those numeric offsets to access each method or field.

Multiple inheritance makes it very tricky.

If class C inherits from both A and B, the compiler has to decide whether to layout the data in AB order or in BA order.

But now imagine that you're calling methods on a B object. Is it really just a B? Or is it actually a C object being called polymorphically, through its B interface? Depending on the actual identity of the object, the physical layout will be different, and its impossible to know the offset of the function to invoke at the call-site.

The way to handle this kind of system is to ditch the fixed-layout approach, allowing each object to be queried for its layout before attempting to invoke the functions or access its fields.

So...long story short...it's a pain in the neck for compiler authors to support multiple inheritance. So when someone like Guido van Rossum designs python, or when Anders Hejlsberg designs c#, they know that supporting multiple inheritance is going to make the compiler implementations significantly more complex, and presumably they don't think the benefit is worth the cost.

Roughshod answered 22/10, 2008 at 14:44 Comment(8)
If you updated your answer with a description in layman terms of the obvious problem with overriding it would be my pleasure to select this answer as accepted.An
These aren't very convincing arguments - the fixed layout thing isn't tricky at all in most languages; in C++ it's tricky because memory isn't opaque and thus you may run into some difficulty with pointer arithmetic assumptions. In languages where class definitions are static (as in java, C# and C++), multiple inheritance name clashes can be forbidden compile time (and C# does this anyhow with interfaces!).Iodate
The OP just wanted to understand the issues, and I explained them without personally editorializing on the matter. I just said that the language designers and compiler implementors "presumably don't think the benefit is worth the cost".Roughshod
"The most obvious problem is with function overriding." This has nothing to do with function overriding. It's a simple ambiguity problem.Candidate
This answer has some wrong info about Guido and Python, since Python supports MI. "I decided that as long as I was going to support inheritance, I might as well support a simple-minded version of multiple inheritance." — Guido van Rossum python-history.blogspot.com/2009/02/… — Plus, ambiguity resolution is fairly common in compilers (variables can be local to block, local to function, local to enclosing function, object members, class members, globals, etc.), I don't see how an extra scope would make a difference.Collate
Are these really the main reasons for languages to not implement multiple inheritance? I mean: the ambiguity is quite easy to solve once one knows there's MI inside the tree.. And in managed environments I think memory layout shouldn't be a driving factor?Infra
I think the OP was asking about MI from the user (programmer) perspective, not the one of the compiler builder.Verbena
@Roughshod I do not see how the memory layout issue that you have mentioned only poses a problem in case of multiple inheritance. It is a issue with single inheritance also, right?Godric
S
50

The problems you guys mention are not really that hard to solve. In fact e.g. Eiffel does that perfectly well! (and without introducing arbitrary choices or whatever)

E.g. if you inherit from A and B, both having method foo(), then of course you don't want an arbitrary choice in your class C inheriting from both A and B. You have to either redefine foo so it's clear what will be used if c.foo() is called or otherwise you have to rename one of the methods in C. (it could become bar())

Also I think that multiple inheritance is often quite useful. If you look at libraries of Eiffel you'll see that it's used all over the place and personally I've missed the feature when I had to go back to programming in Java.

Speedway answered 14/11, 2008 at 22:3 Comment(13)
I agree. The main reason why people hate MI is the same as with JavaScript or with static typing: most people have only ever used very bad implementations of it – or have used it very badly. Judging MI by C++ is like judging OOP by PHP or judging automobiles by Pintos.Bryon
Out of curiosity, what would you say are good languages by which to judge MI?Cilo
The Common Lisp Object System handles MI well (although it's rather idiosyncratic in its approach to OO). If you're into text adventures, now often known as interactive fiction, you'll find languages that rely heavily on MI at ifarchive.org.Parterre
@curiousguy: MI introduces yet another set of complications to worry about, just like many of the "features" of C++. Just because it is unambiguous does not make it easy to work with or debug. Removing this chain since it went off topic and you blew it off anyway.Michaelamichaele
@Michaelamichaele The source of the "complication" here isn't MI, it's virtual inheritance. Indeed virtual inheritance has different semantic WRT non-virtual, like virtual vs. non-virtual functions: virtual functions offer weaker guaranties, but nobody is painting virtual functions as bad. Both virtual and non-virtual are useful, at different times.Candidate
C# already has an requirement for explicit inheritance implementation where the method signatures are the same. Why couldn't a similar approach apply to the class implementation?Wendywendye
@Michaelamichaele the only problem with MI in any language is shitty programmers thinking they can read a tutorial and suddenly know a language.Amii
@MilesRout: Any language feature needs to reduce coding time without significantly increasing bugs. Since encapsulation is quite powerful in any language, the value of MI isn't that high, however the number of bugs can be quite large when you consider any kind of interesting corner cases. I would agree that virtual inheritance is the culprit of bugs, but it is vastly more valuable than MI.Michaelamichaele
I would argue that language features aren't just about reducing coding time. They're also about increasing the expressiveness of a language, and increasing performance.Amii
Also, bugs only occur from MI when idiots use it incorrectly.Amii
what do you mean rename a method if it is defined more than once? to what? sounds like a bad idea to mePinta
Having read this answer, I'm surprised not to see "The problems you guys mention are not really that hard to solve. In fact e.g. Eiffel does that perfectly well!" in an answer to basically every question on Stack Overflow.Northumbrian
Well...if you miss Multiple Inheritance in Java, Inner Classes indirectly provide the support for it...Dogma
A
28

The diamond problem:

an ambiguity that arises when two classes B and C inherit from A, and class D inherits from both B and C. If there is a method in A that B and C have overridden, and D does not override it, then which version of the method does D inherit: that of B, or that of C?

...It is called the "diamond problem" because of the shape of the class inheritance diagram in this situation. In this case, class A is at the top, both B and C separately beneath it, and D joins the two together at the bottom to form a diamond shape...

Arrington answered 22/10, 2008 at 14:30 Comment(8)
which has a solution known as virtual inheritance. It's only a problem if you do it wrong.Cantina
@IanGoldby: Virtual inheritance is a mechanism for solving part of the problem, if one does not need to allow identity-preserving upcasts and downcasts among all of the types from which an instance is derived or for which it is substitutable. Given X:B; Y:B; and Z:X,Y; assume someZ is an instance of Z. With virtual inheritance, (B)(X)someZ and (B)(Y)someZ are distinct objects; given either, one could get the other via a downcast and upcast, but what if one has a someZ and wants to cast it to Object and then to B? Which B will it get?Southard
@Southard Perhaps, but problems like that are largely theoretical, and in any case can be signalled by the compiler. The important thing is to be aware of what problem you are trying to solve, and then use the best tool, ignoring the dogma from people who would rather not concern themselves with understanding 'why?'Cantina
@IanGoldby: Problems like that can only be signaled by the compiler if it has simultaneous access to all the classes in question. In some frameworks, any change to a base class will always necessitate a recompile of all derived classes, but the ability to use newer versions of base classes without having to recompile derived classes (for which one might not have source code) is a useful feature for frameworks that can provide it. Further, the problems aren't just theoretical. Many classes in .NET rely upon the fact that a cast from any reference type to Object and back to that type...Southard
...will be identity-preserving. If one didn't care about being able to use newer versions of base classes without having to recompile all derived classes, and or the ability to use identity-preserving upcasts and downcasts, then virtual inheritance might represent a usable solution to the Diamond Problem. On the other hand, the makers of .NET and Java thought the aforementioned abilities were more useful than MI would be; I see no way to add generalized MI to a framework without giving up one or both of those abilities.Southard
@Southard I totally accept your point in the context of the original question. However, I was replying to a comment that seemed to imply that the only way to avoid the diamond problem is to avoid MI altogether. The truth is much more nuanced, and involves design tradeoffs as you've so well explained.Cantina
@IanGoldby: Fair enough. My point was that the implementers of Java and .NET weren't just "lazy" in deciding not to support generalized MI; supporting generalized MI would have prevented their framework from upholding various axioms whose validity is more useful to many users than MI would be.Southard
You could simply introduce a rule for the function overriding problem, like use the function from the left oneSaba
O
21

Multiple inheritance is one of those things that is not used often, and can be misused, but is sometimes needed.

I never understood not adding a feature, just because it might be misused, when there are no good alternatives. Interfaces are not an alternative to multiple inheritance. For one, they don't let you enforce preconditions or postconditions. Just like any other tool, you need to know when it is appropriate to use, and how to use it.

Olia answered 22/10, 2008 at 14:33 Comment(7)
Can you explain why they don't let you enforce pre and post conditions?Blackwood
@Blackwood because interfaces cannot have method implementations. Where do you put the assert?Candidate
@curiousguy: you use a language with a suitable syntax which allows you to put the pre- and post- conditions directly into the interface: no "assert" needed. Example from Felix: fun div(num: int, den: int when den != 0) : int expect result == 0 implies num == 0;Blackwood
@Blackwood OK, but some languages, like Java, do not support either MI or "pre- and post- conditions directly into the interface".Candidate
It's not used often becouse it's not available, and we don't know how to use it well. If you take a look at some Scala code, you'll see how things start to be common and can be refactored to traits (Ok, it's not MI, but proves my point).Leeuwenhoek
Traits in Scala bear some of the same problems as MI as they are stateful. So the order in which a class is extended by traits also matters, see blog.jetbrains.com/kotlin/2011/08/…Hathorn
I would disagree on the whole "not used often" thing; one very popular web framework is Django, and if you are using one of many different Django plugins, there is a high chance you are using multiple inheritance, take django-braces for example.Staysail
S
17

let's say you have objects A and B which are both inherited by C. A and B both implement foo() and C does not. I call C.foo(). Which implementation gets chosen? There are other issues, but this type of thing is a big one.

Scarification answered 22/10, 2008 at 14:19 Comment(21)
But that's not really a concrete example. If both A and B have a function, it's very likely that C will need it's own implementation as well. Otherwise it can still call A::foo() in it's own foo() function.Alto
@Quantum: What if it doesn't though? It's easy to see the problem with one level of inheritance, but if you have lots of levels and you have some random function that is somewhere twice this becomes a very hard problem.Scarification
Also, the point isn't that you can't call the method A or B by specifying which one you want, the point is that if you don't specify then there's no good way to choose one. I'm not certain how C++ handles this, but if someone knows could the mention it?Scarification
@Scarification - if C does not resolve the ambiguity, the compiler can detect this error and return a compile-time error.Iodate
@Earmon - Due to polymorphism, if foo() is virtual the compiler might not even know at compile-time that this is going to be an issue.Scarification
"the compiler might not even know at compile-time that this is going to be an issue" Can you give an example?Candidate
Hi, could someone tell me why does this situation as described in this answer NOT create a compile time error?Maladjustment
@curiousguy: In many frameworks including both Java and .net, a project will often contain many separately-compiled units which are not combined until code is loaded for execution; one design goal of such frameworks is to allow a class to be recompiled without requiring the recompilation of all classes that either use it or derive from it. As such, if Foo inherits from Moo and Goo, both of which override Boo, and neither Moo nor Goo overrides Boo's virtual function Fred, then there's no ambiguity accessing member Fred of a Foo. On the other hand, ...Southard
...even if the versions of Moo and Goo that existed when Fred was compiled don't override Fred, it's possible that later versions of Moo and Goo might do so. Such conflicts could not be detected until load time; worse, an old Foo might be usable in combination with a new Moo and an old Goo, or an old Moo and new Goo, but not with a new Moo and a new Goo. Some compatibility problems are unavoidable, but MI makes things worse than they would be without it.Southard
@ShayanAli If you try to invoke a method, but which method is designated is ambiguous, there will be compile time error.Candidate
@Southard It is not clear what you are talking about. Can you submit complete minimal code showing the problem?Candidate
@curiousguy: class Boo {public virtual void Fred() {...} ; public virtual void Barney() {...} class Moo:Boo {...} class Goo:Boo {...} class Foo : Moo,Goo { override void Barney() { Fred(); } }. Assume all classes are in separate assemblies. What should calling Barney on a Foo do? Now suppose a new version of Moo adds a Fred override, and Goo does as well. Now what should be the effect of Boo it = new Foo(); it.Barney();?Southard
@Southard I see. Let's write in C++: class Moo: public virtual Boo {...} class Goo: public virtual Boo {...}. When using virtual inheritance, overriding is not an implementation detail. You cannot add an overrider of virtual base virtual function, because you have an overriding contract. virtual and non-virtual inheritance are different, and there isn't one good and one bad inheritance, they have different properties. If you think overriding should be an implementation detail than just use non-virtual inheritance. Of course you will not be able to write Boo it = new Foo(); then.Candidate
"Such conflicts could not be detected until load time" because you are using a broken programming language. In a non-broken language, overriders are declared in the interface files, and C++ is non-broken in this respect. So there isn't a fundamental issue here.Candidate
@curiousguy: I would posit that in general, it's good for a framework to allow newer versions of a class to extend the list of types for which it is substitutable while retaining binary compatibility with older versions. Replacing interfaces with MI would degrade that ability. On the other hand, perhaps a framework could allow MI and include interfaces, with the rule being that for a class to extend the list of classes from which it inherits would break compatibility, but extending the list of interfaces it implements would not; further, require that diamonds would be allowed...Southard
...only if both/all intermediate classes have consistent specifications with regard to all base-class members [e.g. all could promise not to override members of the base class, or all but one could promise to accept sibling overrides]. Changing such specifications for a class would break binary compatibility. In any case, even if generalized MI wouldn't make it impossible to design a framework to allow binary-compatible upgrades to classes, it would certainly make it harder to design a framework which is flexible and robust in that regard.Southard
@Southard Disregarding any binary compatibility issue, programming with multiple virtual inheritance requires discipline; C++ books describe adequate patterns that do not lead to ambiguity; when proper discipline is applied, every virtual function has a final overrider. Programmers need to learn proper use of MI. Many arguments against MI are from people who never learnt these coding practices, and who naively believe they can throw up any set of classes with a common base and no clear specification of derived classes roles.Candidate
... This coding discipline results in valid C++ programs, but the discipline is outside the scope of the language: the roles of derived classes are not described by the type system. This doesn't mean that specific idioms can't be formally described and checked by style checker. Even without this sort of meta type checking tool, programmers use good old test suites, including compile time test suites, to check their changes. The problem you mentioned (adding an override which breaks derived classes) would be detected by these tests.Candidate
@curiousguy: It may be possible for a validator to determine whether changes to an intermediate level class have been restricted to kinds which are unlikely to break binary compatibility with any possible derived class, though unless the framework is designed carefully the kinds of changes that won't risk such breakage would be rather limited. It won't in general be possible to test whether changes to a class will affect any of the classes that actually derive from it, however, because in many cases the author of the intermediate class won't have access to all derivatives.Southard
... These constraints could also be integrated in some evolved programming language as a meta type system. This would mandate error diagnostic by the compiler (no external tool required) as soon as an excess overrider is declared, possibly with a clearer message than with test cases.Candidate
let us continue this discussion in chatCandidate
D
6

I don't think the diamond problem is a problem, I would consider that sophistry, nothing else.

The worst problem, from my point of view, with multiple inheritance is RAD - victims and people who claim to be developers but in reality are stuck with half - knowledge (at best).

Personally, I would be very happy if I could finally do something in Windows Forms like this (it's not correct code, but it should give you the idea):

public sealed class CustomerEditView : Form, MVCView<Customer>

This is the main issue I have with having no multiple inheritance. You CAN do something similar with interfaces, but there is what I call "s*** code", it's this painful repetitive c*** you have to write in each of your classes to get a data context, for example.

In my opinion, there should be absolutely no need, not the slightest, for ANY repetition of code in a modern language.

Dionnadionne answered 6/7, 2010 at 7:57 Comment(4)
I tend to agree, but only tend: there is a need from some redundancy in any language to detect mistakes. Anyhow you should join the Felix developer team because that's a core goal. For example, all declarations are mutually recursive, and you can see forward as well as backwards so you don't need forward declarations (scope is set-wise, like C goto labels).Blackwood
I completely agree with this - I just ran into a similar problem here. People talk about the diamond problem, they quote it religiously, but in my opinion it's so easily avoided. (We don't all need to write our programs like they wrote the iostream library.) Multiple inheritance should logically be used when you have an object that needs the functionality of two differing base classes that have no overlapping functions or function names. In the right hands, it's a tool.Redstone
@Turing Complete: wrt not having any code repetition: this is a nice idea but it is incorrect and impossible. There are a huge number of usage patterns and we desire to abstract common ones into the library, but it is lunacy to abstract all of them because even if we could the semantic load remembering all the names is too high. What you want is a nice balance. Don't forget repetition is what gives things structure (pattern implies redundancy).Blackwood
@lunchmeat317: The fact that code generally shouldn't be written in such a way that the 'diamond' would pose a problem, doesn't mean that a language/framework designer can just ignore the issue. If a framework provides that upcasting and downcasting preserve object identity, wishes to allow for later versions of a class to increase the number of types for which it can be substituted without that being a breaking change, and wishes to allow run-time type creation, I don't think it could allow multiple class inheritance (as opposed to interface inheritance) while meeting the above goals.Southard
N
5

The main problem with multiple inheritance is nicely summed up with tloach's example. When inheriting from multiple base classes that implement the same function or field it's the compiler has to make a decision about what implementation to inherit.

This get's worse when you inherit from multiple classes that inherit from the same base class. (diamond inheritance, if you draw the inheritance tree you get a diamond shape)

These problems are not really problematic for a compiler to overcome. But the choice the compiler has to make here are rather arbitrary, this make code far less intuitive.

I find that when doing good OO design I never need multiple inheritance. In cases I do need it I usually find I've been using inheritance to reuse functionality while inheritance is only appropriate for "is-a" relations.

There are other techniques like mixins that solve the same problems and don't have the issues that multiple inheritance has.

Nevers answered 22/10, 2008 at 14:28 Comment(2)
The compiled doesn't need to make an arbitrary choice - it can simply error out. In C#, what's the type of ([..bool..]? "test": 1)?Iodate
In C++, the compiler never makes such arbitrary choices: it's an error to define a class where the compiler would need to make an arbitrary choice.Candidate
D
4

The Common Lisp Object System (CLOS) is another example of something that supports MI while avoiding the C++-style problems: inheritance is given a sensible default, while still allowing you the freedom to explicitly decide how exactly to, say, call a super's behaviour.

Duiker answered 26/3, 2010 at 8:19 Comment(1)
Yes, CLOS is one of the most superior object systems since the inception of modern computing in maybe even long since past :)Prelacy
S
3

There is nothing wrong in multiple inheritance itself. The problem is to add multiple inheritance to a language that was not designed with multiple inheritance in mind from the start.

The Eiffel language is supporting multiple inheritance without restrictions in a very efficient and productive way but the language was designed from that start to support it.

This feature is complex to implement for compiler developers, but it seems that that drawback could be compensated by the fact that a good multiple inheritance support could avoid the support of other features (i.e. no need for Interface or Extension Method).

I think that supporting multiple inheritance or not is more a matter of choice, a matter of priorities. A more complex feature takes more time to be correctly implemented and operational and may be more controversial. The C++ implementation may be the reason why multiple inheritance was not implemented in C# and Java...

Schecter answered 2/9, 2009 at 14:54 Comment(4)
C++ support for MI is not "very efficient and productive"?Candidate
Actually it's somewhat broken in the sense it doesn't fit in with other features of C++. Assignment doesn't work properly with inheritance, let alone multiple inheritance (check out the really bad rules). Creating diamonds correctly is so hard the Standards committee screwed up the exception hierarchy to keep it simple and efficient, rather than doing it correctly. On an older compiler I was using at the time I tested this and a few MI mixins and implementations of basic exceptions cost over a Megabyte of code and took 10 minutes to compile .. just the definitions.Blackwood
Diamonds is a good example. In Eiffel, the diamond is resolved explicitly. For example, imagine Student and Teacher both inheriting from Person. The Person has a calendar, so both Student and Teacher will inherit this calendar. If you build a diamond by creating a TeachingStudent that inherits from both Teacher and Student, you may decide to rename one of the inherited calendar to keep both calendars available separately or decide to merge them so that it behaves more like Person. Multiple inheritance can be implemented nicely, but it requires a careful design an preferably from the start...Schecter
Eiffel compilers have to do a global program analysis to implement this model of MI efficiently. For polymorphic method calls they use either dispatcher thunks or sparse matrices as explained here. This doesn't mix well with C++'s separate compilation and C#'s and Java's class loading feature.Rosyrot
S
3

The diamond is not a problem, as long as you don’t use anything like C++ virtual inheritance: in normal inheritance each base class resembles a member field (actually they are laid out in RAM this way), giving you some syntactic sugar and an extra ability to override more virtual methods. That may impose some ambiguity at compile-time but that’s usually easy to solve.

On the other hand, with the virtual inheritance it too easily goes out of control (and then becomes a mess). Consider as an example a “heart” diagram:

  A       A
 / \     / \
B   C   D   E
 \ /     \ /
  F       G
    \   /
      H

In C++ it is entirely impossible: as soon as F and G are merged into a single class, their As are merged too, period. That means you may never consider base classes opaque in C++ (in this example you have to construct A in H so you have to know that it present somewhere in the hierarchy). In other languages it may work, however; for example, F and G could explicitly declare A as “internal,” thus forbidding consequent merging and effectively making themselves solid.

Another interesting example (not C++-specific):

  A
 / \
B   B
|   |
C   D
 \ /
  E

Here, only B uses virtual inheritance. So E contains two Bs that share the same A. This way, you can get an A* pointer that points to E, but you can’t cast it to a B* pointer although the object is actually B as such cast is ambiguous, and this ambiguity can’t be detected at compile time (unless the compiler sees the whole program). Here is the test code:

struct A { virtual ~A() {} /* so that the class is polymorphic */ };
struct B: virtual A {};
struct C: B {};
struct D: B {};
struct E: C, D {};

int main() {
        E data;
        E *e = &data;
        A *a = dynamic_cast<A *>(e); // works, A is unambiguous
//      B *b = dynamic_cast<B *>(e); // doesn't compile
        B *b = dynamic_cast<B *>(a); // NULL: B is ambiguous
        std::cout << "E: " << e << std::endl;
        std::cout << "A: " << a << std::endl;
        std::cout << "B: " << b << std::endl;
// the next casts work
        std::cout << "A::C::B: " << dynamic_cast<B *>(dynamic_cast<C *>(e)) << std::endl;
        std::cout << "A::D::B: " << dynamic_cast<B *>(dynamic_cast<D *>(e)) << std::endl;
        std::cout << "A=>C=>B: " << dynamic_cast<B *>(dynamic_cast<C *>(a)) << std::endl;
        std::cout << "A=>D=>B: " << dynamic_cast<B *>(dynamic_cast<D *>(a)) << std::endl;
        return 0;
}

Moreover, the implementation may be very complex (depends on language; see benjismith’s answer).

Stratfordonavon answered 22/5, 2016 at 21:54 Comment(1)
That's the real problem with MI. Programmers may need different resolutions within one class. A language-wide solution would limit what is possible and force programmers to create kludges to get the program to work correctly.Apnea
S
2

One of the design goals of frameworks like Java and .NET is to make it possible for code which is compiled to work with one version of a pre-compiled library, to work equally well with subsequent versions of that library, even if those subsequent versions add new features. While the normal paradigm in languages like C or C++ is to distribute statically-linked executables that contain all of the libraries they need, the paradigm in .NET and Java is to distribute applications as collections of components that are "linked" at run-time.

The COM model which preceded .NET attempted to use this general approach, but it didn't really have inheritance--instead, each class definition effectively defined both a class and an interface of the same name which contained all its public members. Instances were of the class type, while references were of the interface type. Declared a class as deriving from another was equivalent to declaring a class as implementing the other's interface, and required the new class to re-implement all public members of the classes from which one derived. If Y and Z derive from X, and then W derives from Y and Z, it won't matter if Y and Z implement X's members differently, because Z won't be able to use their implementations--it will have to define its own. W might encapsulate instances of Y and/or Z, and chain its implementations of X's methods through theirs, but there would be no ambiguity as to what X's methods should do--they'd do whatever Z's code explicitly directed them to do.

The difficulty in Java and .NET is that code is allowed to inherit members and have accesses to them implicitly refer to the parent members. Suppose one had classes W-Z related as above:

class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z  // Not actually permitted in C#
{
  public static void Test()
  {
    var it = new W();
    it.Foo();
  }
}

It would seem like W.Test() should creating an instance of W call the implementation of virtual method Foo defined in X. Suppose, however, that Y and Z were actually in a separately-compiled module, and although they were defined as above when X and W were compiled, they were later changed and recompiled:

class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }

Now what should be the effect of calling W.Test()? If the program had to be statically linked before distribution, the static link stage might be able to discern that while the program had no ambiguity before Y and Z were changed, the changes to Y and Z have made things ambiguous and the linker could refuse to build the program unless or until such ambiguity is resolved. On the other hand, it's possible that the person who has both W and the new versions of Y and Z is someone who simply wants to run the program and has no source code for any of it. When W.Test() runs, it would no longer be clear what W.Test() should do, but until the user tried to run W with the new version of Y and Z there would be no way any part of the system could recognize there was a problem (unless W was considered illegitimate even before the changes to Y and Z).

Southard answered 8/4, 2013 at 15:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.