![](/i/fill.gif) |
![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Nicolas Alvarez <nic### [at] gmail com> wrote:
> Warp wrote:
> > I really can't understand what would be the problem in supporting
> > full-fledged multiple inheritance.
> >
> > If your answer contains the term "diamond inheritance", then forbid
> > diamond inheritance, not multiple inheritance.
> If you add multiple inheritance to a language where every class inherits
> from a common base class (Object), every time you multiple-inherit you're
> getting a diamond.
Then don't make every class inherit from a common base class. Interfaces
don't, so why should classes?
If you really want must have classes inherit from Object, then simply
allow interfaces to have member variables and function implementations.
Then you'll have full-fledged multiple inheritance without diamond
inheritance.
--
- Warp
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Warp wrote:
> I really can't understand what would be the problem in supporting
> full-fledged multiple inheritance.
Complexity, perhaps.
Also, remember the CIL is designed to support multiple languages. Granted,
you're going to have trouble accessing objects from COBOL, but it's likely
that MI would just make it that much more difficult. There's already kludgey
work-arounds for (e.g.) getting to properties and indexers from languages
that don't have properties.
> If your answer contains the term "diamond inheritance", then forbid
> diamond inheritance, not multiple inheritance.
Impossible if everything inherits from Object. :-)
> (Could this be, perhaps, related to garbage collection? It makes GC a lot
> more complicated if references don't always point to the beginning of the
> allocated memory block?)
That's certainly part of it, but you don't need to support multiple
inheritance in the same way C++ does it. Eiffel has multiple inheritance,
garbage collection, and O(1) virtual dispatch. (That last would probably be
quite difficult if you didn't compile everything at the same time, of course.)
C# allows you to inherit from multiple interfaces that define the same
method names and distinguish between them at compile time and runtime.
(I.e., you can already have a Dog::Run interface and a Thread::Run interface
and invoke them that way and have different implementations.) So it's not
just a problem with the metadata and naming. (Eiffel has a different
solution to this problem.)
Some of the rules of the language are based on superclasses. If you throw an
object, it has to descend from Exceptions. A class with value semantics has
to descend from "Values" or something like that. A delegate (like an OO
function pointer) has to descend from Delegate. Etc. Maybe that's part of
the problem.
But really, I don't think there are any obvious technical problems that
couldn't be solved with enough work.
--
Darren New, San Diego CA, USA (PST)
Yes, we're traveling together,
but to different destinations.
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Warp wrote:
> Then don't make every class inherit from a common base class. Interfaces
> don't, so why should classes?
Because then you can put routines on that common base class that are common
to every class, like ToString() and Equals() and HashCode() and such.
> If you really want must have classes inherit from Object, then simply
> allow interfaces to have member variables and function implementations.
> Then you'll have full-fledged multiple inheritance without diamond
> inheritance.
That certainly sounds like a good idea to me. :-)
Of course, the way it works now is that an interface doesn't actually change
what's in the class. It's more a promise that the class implements a
particular set of routines. So it's not unlike a pure abstract class. If you
already implement a routine called Run, whether you inherit from Runnable
doesn't change what's in your class, only what's in your metadata. So adding
that feature isn't as much of a no-brainer as it might otherwise be.
On the other hand, maybe it wouldn't be a bad idea to have inheriting from
an interface actually insert code into your class as necessary. It would
certainly be a trivial way to make that work in C# at least, if not in other
languages based on the CIL.
Where have you found multiple inheritance particularly useful in C++? Eiffel
uses it all the time to good effect in its data structures, but I've never
really seen anything in C++ that used it, in part maybe because of the way
it's implemented in C++ with classes nested inside other classes and such
and not being able to inherit from basic types.
--
Darren New, San Diego CA, USA (PST)
Yes, we're traveling together,
but to different destinations.
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Darren New <dne### [at] san rr com> wrote:
> Because then you can put routines on that common base class that are common
> to every class, like ToString() and Equals() and HashCode() and such.
I think that a class for which it doesn't make any sense to have something
like "ToString()" it would be bad design to force it to have it after all.
> On the other hand, maybe it wouldn't be a bad idea to have inheriting from
> an interface actually insert code into your class as necessary. It would
> certainly be a trivial way to make that work in C# at least, if not in other
> languages based on the CIL.
I wonder if it would make garbage collection more complicated because now
you can have references pointing to the middle of an object rather than the
beginning (and it may be the *only* reference pointing to that object, so
the object must not be collected as long as this reference exists, but it
may be more complicated for the GC engine to know this).
> Where have you found multiple inheritance particularly useful in C++?
Wherever implementing multiple interfaces (or a base class and one or
more interfaces) is useful in any OO language, in cases where it would
actually be useful if the "interface" methods could have default
implementations, or if it would be useful if the interface provided
auxiliary methods for the derived class to use.
The problem with "interfaces" is that they are not *helpful* at all.
The don't offer any functionality, any default implementations or any
data. These "interfaces" could often become a lot more helpful if they
actually offered something for the derived class so that it doesn't have
to implement everything from scratch every time.
I'm not saying that every single program I have written (or even a
significant amount of them) uses multiple inheritance, but it has been
useful sometimes.
--
- Warp
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Darren New <dne### [at] san rr com> wrote:
> That's certainly part of it, but you don't need to support multiple
> inheritance in the same way C++ does it. Eiffel has multiple inheritance,
> garbage collection, and O(1) virtual dispatch. (That last would probably be
> quite difficult if you didn't compile everything at the same time, of course.)
As I have mentioned before, you can cast an object pointer to an
incompatible type assuming that:
1) Both the source and destination types have a virtual table (in C++ it's
created by making at least one member function virtual; usually at least the
destructor should be in these cases).
2) The actual object to which this pointer is pointing to has been
multiple-inherited from both the source and destination types (even if
by a very long inheritance chain).
The dynamic cast checks at runtime whether the casting is ok or not
(if it isn't, it returns a null pointer). If the object is of a type
which has been multiple-inherited from both, the dynamic cast will return
a pointer which will point to the destination type part inside the object.
Curiously, the code which performs the cast doesn't need to know that
the multiple-inherited class even exists or what it looks like, and the
cast will still work properly.
I wonder if Eiffel supports this as well.
--
- Warp
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Warp wrote:
> I think that a class for which it doesn't make any sense to have something
> like "ToString()" it would be bad design to force it to have it after all.
I think there's a reasonable "ToString()" for every class. There aren't many
routines in Object, but the ones there are pretty universal. "What type am
I?" "What code defines this type?" "What's my address?" Stuff like that.
In the CIL, I haven't memorized exactly what's there, and some stuff like
that might be in other libraries. But it's usually pretty universal.
> I wonder if it would make garbage collection more complicated because now
> you can have references pointing to the middle of an object rather than the
> beginning (and it may be the *only* reference pointing to that object, so
> the object must not be collected as long as this reference exists, but it
> may be more complicated for the GC engine to know this).
For something managed, I think that would be the wrong way to implement MI.
It's still just one object, even if it inherits data and code from multiple
classes. For example, Python has MI and doesn't have pointers into the
middle of objects.
You could also do it as a sort of "automatic delegation", where each of the
multiple parent classes is represented as a separate object on the heap, for
example, and the child class delegates all the non-overridden calls to the
parent class. In this case, you'd have no significant overhead if the parent
class contributed only code (as the JIT would optimize out the additional
redirection on first invocation). You might be able to also jit-compile the
multiple classes and rearrange their code to use different offsets for the
instance variables when you load the CIL code for a class that has multiple
parents, for example.
The only reason C++ carries pointers to the middle of objects (as far as I
can tell) is that they can't rewrite the object code to use different
offsets in the child class compared to the parent class, and of course
because it can since there's no memory compaction.
>> Where have you found multiple inheritance particularly useful in C++?
>
> Wherever implementing multiple interfaces (or a base class and one or
> more interfaces) is useful in any OO language, in cases where it would
> actually be useful if the "interface" methods could have default
> implementations, or if it would be useful if the interface provided
> auxiliary methods for the derived class to use.
So the "mix in" sort of inheritance. OK.
> The problem with "interfaces" is that they are not *helpful* at all.
> The don't offer any functionality, any default implementations or any
> data. These "interfaces" could often become a lot more helpful if they
> actually offered something for the derived class so that it doesn't have
> to implement everything from scratch every time.
That would definitely be useful, yes. Maybe a later version will add it.
Probably right after the folks working on Java do. ;-)
--
Darren New, San Diego CA, USA (PST)
Yes, we're traveling together,
but to different destinations.
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Warp wrote:
> Darren New <dne### [at] san rr com> wrote:
>> That's certainly part of it, but you don't need to support multiple
>> inheritance in the same way C++ does it. Eiffel has multiple inheritance,
>> garbage collection, and O(1) virtual dispatch. (That last would probably be
>> quite difficult if you didn't compile everything at the same time, of course.)
>
> As I have mentioned before, you can cast an object pointer to an
> incompatible type assuming that:
Right. I'm not sure what that had to do with what I was saying.
I was trying to say that Eiffel holds only one pointer to any given object,
and that points to the start of the instance. This way, Eiffel gets simpler
GC. It also does multiple inheritance in fairly complex ways, and has an
efficient dispatch to handle it (i.e., no run-time lookups up the
inheritance chain).
I believe the mechanism relies on knowing at compile time what every class
in the system will be. You don't get the O(1) lookup in Eiffel if you don't
have the source to all the classes you inherit from when you compile the
class. You might not even get it if you don't have the source code for those
classes when you compile the callers, but I'm not sure.
> I wonder if Eiffel supports this as well.
Eiffel lets you cast up and down the heirarchy. All methods are semantically
virtual (even if the compiler optimizes them to direct jumps), so the fact
that you're casting up and down doesn't make it "invalid". You can't cast a
reference to an object to a type that isn't in that object's parent class
heirarchy.
There is one class called "ALL" that everyone inherits from. There is one
class called "NONE" that inherits from everyone, and which has only one
instance, called "Null". :-) So NULL can cast to any type, and any type can
cast to "ALL" (which serves as Object).
--
Darren New, San Diego CA, USA (PST)
Yes, we're traveling together,
but to different destinations.
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Darren New wrote:
> I think there's a reasonable "ToString()" for every class.
Oh, and it's definitely helpful to have a class to which everything else can
be cast. Imagine C if you didn't have the functionality of void*.
--
Darren New, San Diego CA, USA (PST)
Yes, we're traveling together,
but to different destinations.
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Darren New <dne### [at] san rr com> wrote:
> For something managed, I think that would be the wrong way to implement MI.
> It's still just one object, even if it inherits data and code from multiple
> classes. For example, Python has MI and doesn't have pointers into the
> middle of objects.
That sounds like it would be somewhat inefficient. If the pointer always
points to the beginning of the object, then it would always need to resolve
the offset to the members it wants every time it wants to access them
(because in a multiple-inherited object the members of the base class you
want do not necessarily start from the beginning of the object the pointer
is pointing to, but could start from anywhere).
> You could also do it as a sort of "automatic delegation", where each of the
> multiple parent classes is represented as a separate object on the heap, for
> example, and the child class delegates all the non-overridden calls to the
> parent class.
That sounds like you would need several steps of indirection in order
to access a base class member using a derived class pointer. And checks
for this would need to be done for *all* accesses, even in situations where
there's no inheritance at all (because the code which tries to access the
object through the pointer cannot know if the object it was given was a
derived object or not, without always checking).
> The only reason C++ carries pointers to the middle of objects (as far as I
> can tell) is that they can't rewrite the object code to use different
> offsets in the child class compared to the parent class, and of course
> because it can since there's no memory compaction.
It's more efficient when code which handles objects of a certain type
doesn't need to perform any checks to see if the object is really of that
precise type, or whether it's some inherited type. From the point of view
of that code the object looks exactly like if it was exactly of that
precise type (all the member offsets are always the same) regardless of
what the actual type of the object is.
(Things like virtual inheritance can complicate this a bit, though.)
> Probably right after the folks working on Java do. ;-)
Well, the Java people added their crippled version of templates to the
language regardless of all the arguments why templates are bad, so maybe
there's hope that MI will also be added, regardless of all the arguments
why it's bad.
(It has always amused me how in the dawn of Java people constantly
preached how Java was like "C++ done right", yet it seems to lack most
things which are *good* and/or useful in C++, like RAII, destructors,
typedefs, enums, templates, multiple inheritance, handling objects by
value, operator overloading and whatnot.)
--
- Warp
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Darren New <dne### [at] san rr com> wrote:
> Darren New wrote:
> > I think there's a reasonable "ToString()" for every class.
> Oh, and it's definitely helpful to have a class to which everything else can
> be cast. Imagine C if you didn't have the functionality of void*.
It's useful mostly if you have some way of casting back to the original
type.
--
- Warp
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |