Multiple inheritance problem

Started by
29 comments, last by Polymorphic OOP 19 years, 7 months ago
Quote:Original post by Polymorphic OOP
For instance, now you have your wrapped NewInterface in a child of the OldInterface. So you return it and have a pointer to the OldInterface. Now how do you convert the OldInterface pointer from outside of that function to the NewInterface (remember that externally you can potentially have no knowledge of those additional types, as rsegal has claimed)? This is a situation that very well can occur and is what is described in the original posters problem as well as in my suggested scenerio.

NewInterface and OldInterface are not related. If you want a NewInterface you can get one from the application that gave you the OldInterface.

Quote:
In addition to the increase in the amount of code and the fact that the problem isn't fully solved, you are also using more dynamically allocated objects than you'd use with the suggested multiple inheritance model, which would only have the dynamic allocation of the child object (assuming you'd even need that object dynamically allocated, which isn't necessarily the case either). What's more is, the multiple inheritance model is most-likely much easier to understand since all you are doing is instantiating a child and interfacing with it via its bases, just like with any other public inheritance. No extra object types are introduced. All you need to know is C++ and the fact the the conversion is theoretically possible (unless, of course, you are working across executables as I described in my previous reply).

So the multiple inheritance model can be implemented in less code, without the addition of new types, and uses built-in functionality of the language making it less-likely for you to make an error (since the bulk of the work is done automatically by the compiler and not by a human who can understandably make mistakes).


Let's look at it another way. What you suggest is to make it easier to add new functionality as the inheritance will look after the support of old interfaces. Imagine you've got more than two versions. Imagine you have OldInterface, NewInterface, BrandNewInterface, LatestInterface, NextBigThingInterface, etc. Using it in this way will mean you end up with a class inheriting from all previous versions. This doesn't look good to me. It is difficult and error prone to understand what code is implementing what functionality for what version of the interface.

With my way of doing it ("he said d-oing"), your dependency upon old and legacy code is taken care of in one place, through a delegating class. Your new code really is new code. If you want to reuse some of the older code you can contain an object and use its functionality instead of inheriting it.

If it's not possible to use the new class with the limited old interface it won't be possible to write the delegating wrapper. I feel this better expresses the development of an interface. With your design it would be possible to get a handle on an object but not be able to meaningfully use it.
Advertisement
Quote:Original post by petewood
NewInterface and OldInterface are not related. If you want a NewInterface you can get one from the application that gave you the OldInterface.


But they are related in the sense that they both can interface with the same object, they can both be represented in the same hierarchy, and in different areas of a program you may need to interface with the same object via both (or multiple) different interfaces (this is one of the reasons why Java allows multiple inheritance with interfaces). This exact concept is directly representable with multiple inheritance with the types being sister types. The problem is that you say you can just get a new interface from the place you got the old interface, but this isn't always a possibility (not to mention the fact it's an unecessary hassle).

For instance, just because you get a pointer to an interface from one location, that doesn't mean it's the place it was created, as well, what if you were passed the pointer via function arguments -- you have no idea who passed it to you. You would always have to couple the interface pointer with information about who created it and be able to convert to that type (and potentially other interface types) via that information, otherwise you wouldn't have a way to make the conversion (since you aren't guaranteed that anyone else knows how).

The further you go with the aggregation concept in this situation, the harder you make it on yourself. In the end, you're just killing yourself making large amounts of additional functions and types just to emulate something that you can already do with built-in functionality of the language.

What exactly do you think is so bad about the pointer casting? In places where it actually makes sense to use it, such as here, it provides pretty much the most concise solution -- it certainly beats most of the alternatives, with one of the only other viable solutions being the Query concept (which often internally uses multiple inheritance anyways).

Quote:Original post by petewood
Let's look at it another way. What you suggest is to make it easier to add new functionality as the inheritance will look after the support of old interfaces. Imagine you've got more than two versions. Imagine you have OldInterface, NewInterface, BrandNewInterface, LatestInterface, NextBigThingInterface, etc. Using it in this way will mean you end up with a class inheriting from all previous versions. This doesn't look good to me. It is difficult and error prone to understand what code is implementing what functionality for what version of the interface.


Are you saying that simply multiply inheriting from them is more difficult to understand and more error prone than using your solution (which again, still needs more work in order to match the capabilities of cross-casting). Multiple inheritance takes one line of code, no extra functions, and any complex work is done automatically by the compiler. To make the object and have it convertible to the other interfaces it's one line of code. To actually perform the conversion it's one line of code. There is a lot more that can go wrong by not using multiple inheritance since now you are forced to use your own rolled techniques and a lot of uneeded extra code. Even moreso, the multiple inheritance version is probably going to result in faster conversion. The only potential problem is that when you start getting into a very large amount of interfaces then you can bloat the size of the object due to the fact that each base will need its own vtable pointer, and it will force longer construction due to vtable pointer initialization. That's when it might be wise to consider more dynamic forms of interface "conversion" such as, again, via a Query mechanism.

Quote:Original post by petewood
If you want to reuse some of the older code you can contain an object and use its functionality instead of inheriting it.


But you have yet to answer why you feel encapsulation makes more sense here over inheritance. You keep saying it as if encapsulation is always a better alternative to inheritance, which isn't true. In fact, this situation is a prime example for when to use inheritance over encapsulation. And again, in the current state of your solution you still aren't providing all of the power that multiple inheritance with cross-casting can provide, nor can you directly perform the conversion that was required in the original post (be able to convert between the types with no further knowledge of things such as who created it).

Quote:Original post by petewood
With your design it would be possible to get a handle on an object but not be able to meaningfully use it.


How so? This is never the case -- if the conversion can't take place it won't give you a meaningless handle, your attempt at conversion will just fail. Your dynamic_cast will return 0, or throw an exception, or your Query function will not be successful. You'll never be put in a place where you have a handle to an object you can't meaningfully use.

I know I keep repeating myself by saying "like QueryInterface with COM" but I'm doing so with reason. The Query concept provides everything you need very simply, leaving you open for use of multiple inheritance or other methods of conversion. Querying in conjunction with multiple inheritance is the quickest, easiest, and probably fastest implementation you can get that satisfies all of the requirements that are given, and in practical use, it works across multiple executables.
Quote:Original post by Polymorphic OOP
I know I keep repeating myself by saying "like QueryInterface with COM" but I'm doing so with reason. The Query concept provides everything you need very simply, leaving you open for use of multiple inheritance or other methods of conversion. Querying in conjunction with multiple inheritance is the quickest, easiest, and probably fastest implementation you can get that satisfies all of the requirements that are given, and in practical use, it works across multiple executables.

OK, I'm having to get used to COM and QueryInterface at work now so I can see where you're coming from.
However this kind of thing isn't what I'd expect to be used in games. It just has that "sledgehammer to hit in a nail" feeling about it. Who knows, maybe it is a good idea.

One question though... Do you, or have you ever worked for Microsoft?
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
I'm trying to work out how to reply. I don't want to cut your post up and reply piecemeal as it probably won't make a coherent answer. So I'll focus on one thing you've said. This seems to sum up everything I have a problem with:

Quote:Original post by Polymorphic OOP
Multiple inheritance takes one line of code, no extra functions, and any complex work is done automatically by the compiler. To make the object and have it convertible to the other interfaces it's one line of code. To actually perform the conversion it's one line of code.


This is unlikely to be true.

Let's take two different cases:
One is where the classes that are being inherited are pure abstract interfaces - i.e. they have no data members and no implementation. You inherit so that you have that interface.

The other is where the class you are inheriting have some implementation and data. You are inheriting to reuse the implementation and to possibly customise it through overriding virtual functions and also to have the interface.

If you take the first case, simply adding one line of code to inherit from another base class will make it possible to convert between two different base class pointers using dynamic_cast. But you still have to provide an implementation. So it isn't as simple as adding one line of code.

If you take the second case, simply adding one line of code to inherit the interface and the implementation will mean you don't have to provide any implementation and you can convert between two different base pointers using dynamic_cast. However, if you haven't implemented anything yourself then the new class you have with two bases with implementation code don't actually interact in any way. It is as though you have two classes stuck together but they actually keep themselves to themselves, all their data is their own, they don't use each others functions etc. So if you are using the interface provided by one base class you will only be using the functions and data from that base class implementation. If you use a pointer to the other base class, again you will only use those functions and data.

If you want to actually have it that you are benefiting from inheriting from both, you need to have functions in the derived class which make use of functions from both bases. Otherwise the class is two classes lumped together.

I hope you can see that it's not as simple as just deriving from another base. The extra code I have had to type is little more than the extra code you would have to type to actually make use of the inheritance in a meaningful way.

Pete
Thanks for the responses everyone. It looks like dynamic cast is working out for me. It seems to handle the crosscasting of my pointers quite nicely.

I've been reading up on dynamic cast and and if I've got it right the dynamic_cast cast allows you to downcast a pointer which was created as a derived class but stored as a base class back to the dervied class type safely. I'm meaning downcast to cast from a base class to a dervied class, perhaps that's incorrect terminology. I'm sure someone will correct me if I am mistaken.

So in my example off of my first post where I have the inhertiance structure

A E
| |
B D
\ /
C

I create objects of class C, store them as pointers of class D and then dynamic cast them later on to pointers of class B. This seems to work for me and does it make sense to me that an instance of class C should be able to be cast to objects of classes A, B, D or E.
Rob Segal - Software Developerrob@sarcasticcoder.com
in your example an instance of C should be able to cast to: A, B, C, D, E.

one thing I don't know for sure about is if you can do what you want all in one step (assume that the REAL instance type in all following examples is C unless otherwise stated)... If you have a pointer of type D - you can definately use it as E directly, and use dyanmic_cast to cast is to C (which will simply return 0 if the object isn't really of type C). If you have a pointer of type C, you can definately use it as types A,B,C,D,E directly. If you have a pointer of type E, you can definately dynamic_cast it to E, D, or C (each will return 0 if the instance is not compatible, or succeed otherwise).

However, what I don't know about is if the compiler is supposed to (according to the standard) let you dyanmic_cast from a D to an A or B. Because those two classes are NOT statically related to each other in any way, and the compiler I believe is free to choose methods of RTTI that would not work in such cases (the fact that a particular compiler might allow it may not indicate that the standard does). My feeling is that in the following example:

CurrentType *objectPointer;
DesiredType *newObjectPointer;

newObjectPointer = dynamic_cast<DesiredType*>(objectPointer);

the compiler is free to yield a compiler error or warning and refuse to compile the code if the DesiredType is not directly a base or derived class of the CurrentType ...

but I might be wrong.

If I am right, it would mean your would have to do something like this:

D *object;
B *result;

// here's the version that may not be guaranteed to compile
result = dyanmic_cast<B*>(object);

// here's are 3 versions that will compile
result = dynamic_cast<B*>(dynamic_cast<C*>(object)); // go from D to C, then C to B
result = (B*)dynamic_cast<C*>(object); // go from D to C using dynamic, then C to B using the fact that C is derived from B
result = dynamic_cast<C*>(object); // go from D to C using dynamic, then store a C in a B automatically because of inheiritance (is a) relationship

reguardless of how many dozens of ways to write it and error trap it there might be ... the key is, you really only need to go from A/B/E/D to C using dynamic cast ... and then can get from there back to A/B/E/D by the sheer fact that C inheirits from them all (in other words, dyanmic cast is only needed to go downstream, not upstream).
the link i posted about cross casting explains that dynamic_cast will work as he wishes.
As i've already shown in my previous post you don't need to go through any intermediate steps, this is what dynamic_cast does navigates type hierarchies not just vertically but also horizontally & diagonally in the type hierarchy so as long as the pointer to type E refers to an instance of type C it you can directly cast to a pointer of type A because C is related to type E & A in the type hierarchy.
Quote:Original post by petewood
the link i posted about cross casting explains that dynamic_cast will work as he wishes.


Yes it did explain what I was looking to find out, thanks pete.

Quote:Original post by snk_kid
As i've already shown in my previous post you don't need to go through any intermediate steps, this is what dynamic_cast does navigates type hierarchies not just vertically but also horizontally & diagonally in the type hierarchy so as long as the pointer to type E refers to an instance of type C it you can directly cast to a pointer of type A because C is related to type E & A in the type hierarchy.


Yes that's what I've found. dynamic_cast is ANSI C++ right? I'm guessing you only have to enable RTTI for it work under MS Visual Studio?
Rob Segal - Software Developerrob@sarcasticcoder.com
Quote:Original post by rsegal
Quote:Original post by snk_kid
As i've already shown in my previous post you don't need to go through any intermediate steps, this is what dynamic_cast does navigates type hierarchies not just vertically but also horizontally & diagonally in the type hierarchy so as long as the pointer to type E refers to an instance of type C it you can directly cast to a pointer of type A because C is related to type E & A in the type hierarchy.


Yes that's what I've found. dynamic_cast is ANSI C++ right? I'm guessing you only have to enable RTTI for it work under MS Visual Studio?


This is the standard C++ way & only proper way to navigate type hierarchies.

You make the compile's life hard if you use C-style casts it can't tell what your intent is, it could mean any kind of casting, and it would probably just decide to do something simillar to what reinterpret_cast does, if you use it for that purpose, not good just don't use it in C++.

Yeah you need to turn on RTTI for visual studio.

This topic is closed to new replies.

Advertisement