• Advertisement

Archived

This topic is now archived and is closed to further replies.

On Multiple Inheritance and Design Issues (controversy!)

This topic is 5001 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I was reading the C++ FAQ and came across this: Some people believe that the purpose of inheritance is code reuse. In C++, this is wrong. Stated plainly, "inheritance is not for code reuse." The purpose of inheritance in C++ is to express interface compliance (subtyping), not to get code reuse. I've been C++ing for about 2 years and i've learned a lot. However, i disagree with the above statement (or i don't understand it). Perhaps i've learned some bad habits, and i can admit that, but quite frankly, i have no good reason to believe the above statement. I thought it might be good to open up a discussion on it on what good practice is and is not. The reason i bring it up is because i here a lot of "this is the right way" or "this is bad", but no real reason is given. So i have to assume it's just so-and-so's opinion. ------------------------------------- Now for my particular game, i use inheritance a LOT and i make good use a multiple inheritence as well. I choose to do this for a variety of reasons: - Polymorphism is cool, and i use NEW pointers more than i should. - I need a common base class to lump many things together in lists. I maintain several different lists over which i can iterate including update objects, collision objects, etc. - I compartmentalize an object's functionality with classes and patch them together with multiple inheritence. For instance (and please tell me if you think this is good or bad): I have a "player". A player is a - UpdateObject (used to call object->Update() every frame) - CollisionObject (of course) - LifeObject (has hit points and can die) - PhysicalObject (has a location, speed, weight, and vector) So what i do is program each of these classes seperately. Then i patch them all together to create a "Player" class like so:
class Player: public UpdateObject, public CollisionObject, public LifeObject, public PhysicalObject {
// override functions and whatnot
}   
pretty much all my objects are this way. I like this because i can isolate functionality to seperate classes and create a new object which incorporates that functionality. The other plus is that i can now pass off Player* as a CollisionObject or an UpdateObject or whatever. This is a large part of my overall architectural strategy. The above FAQ and many other sources incourage composition. I'm not real keen on that to be honest. It seems to me, in my mind, that "Player" *IS* all of those other classes, it doesn't contain them. It embodies their collective usage. What do you think? Is my strategy sound? ------------------------------------------ Question to all: on composition, lets say i have a car with an engine:
class Engine {
public:
   void ChangeOil();
private:
   int oil_level;
};

class Car {
public:
   void Drive();
   void Stop();
   void GasUp():
private:
   Engine* engine;
};   
And i want to ChangeOil() in the Car. Does car need to have a "wrapper" function to do that?
public:
   ChangeOil() {
      engine->ChangeOil();
      }   
Is that the "right" way to make composition? I hate wrapper functions! The alternative is to make the engine public and call it directly. I can see how people might frown on that, but you can see how it saves on useless wrapper functions! [edited by - leiavoia on June 11, 2004 1:59:04 AM]

Share this post


Link to post
Share on other sites
Advertisement
Templates are for code reuse. Inheritance might give some code reuse, but it''s MAINLY for dynamic typing, so that you can do things like this:


class CObject
{

public:
CObject();
virtual void DoSomethingCool();
};

class CSubObject : public CObject
{
public:
CSubObject();
void DoSomethingCool();
};

class CSubObject2 : public CObject
{
public:
CSubObject2();
void DoSomethingCool();
};

class CObjectFactory
{
public:
void MakeObjectDoSomethingCool(CObject* pObject)
{
pObject->DoSomethingCool();
}
};

int main()
{
CObjectFactory Factory;
CSubObject Object1;
CSubObject2 Object2;
CObjectFactory.MakeObjectDoSomethingCool(&Object1);
CObjectFactory.MakeObjectDoSomethingCool(&Object2);
}


Which is the single best reason to use it, and probably the most powerful feature of the language. I could derive 50 different objects from CObject, and they''d ALL have their specific implementation of DoSomethingCool() called when I passed them to MakeObjectDoSomethingCool().

For code reuse, use templates. They''re far better suited.

Share this post


Link to post
Share on other sites
Oh, and the main issue with multiple inheritence (which can be useful - though I honestly think you''re probably overdoing it) is the so-called "dreaded diamond":


class Class1
{
virtual void DoSomething();
};

class ClassA : public Class1
{
};

class ClassB : public Class1
{
};

class Class2 : public ClassA, public ClassB
{
};


What happens when you call Class2.DoSomething()? Does it call ClassA.DoSomething? ClassB.DoSomething? The virtual base''s DoSomething? The derived class'' DoSomething?

Not at all pretty.

What you''re doing right now is a bad idea mainly because it looks like you''re creating a seperate class for pretty much every piece of functionality that you could ever have, even though that functionality would probably be better suited as simply a flag in the object itself. For example, wouldn''t it follow that ALL CollisionObjects must be PhysicalObjects? Wouldn''t it make sense that all CollisionObjects are also UpdateObjects?

Your problem isn''t that you''re doing things completely wrong, it''s just that you''re not deriving enough -> you can derive a class from a derived class, and here, you probably should (unless you genuinely like having to tell every player that they''re both a CollisionObject and a Physical object, which seems sort of redundant, doesn''t it?

Share this post


Link to post
Share on other sites
Well, i hear what you''re saying. There are some instances where i want something to have one functionality and not the other, other instances where i want both.

For instance, you mentioned CollisionObjects and PhysicalObjects. For most cases, they go together. However, there are many physical objects (like particles) that have a graphic representation and spatial orientation, but never collide with anything. Therefore, why give it all the overhead of a CollisionObject if i never intend to smash it into anything?

Going along with my design philosophy, if i really did want a colliding particle, guess what i''d do?

class CollisionParticle: public Particle, public CollisionObject {}

now it has all the benefits of being a physical object and particle...

particle->Move();
particle->Draw();

... plus all the jiggers CollisionObjects naturally get...

particle->CheckForCollisions();

as for common interfaces, yes i make good use of that. And as for diamonds, they really bite but are sometimes unavoidable.

Share this post


Link to post
Share on other sites
quote:
Original post by leiavoia
- I compartmentalize an object''s functionality with classes and patch them together with multiple inheritence. For instance (and please tell me if you think this is good or bad):

I have a "player". A player is a
- UpdateObject (used to call object->Update() every frame)
- CollisionObject (of course)
- LifeObject (has hit points and can die)
- PhysicalObject (has a location, speed, weight, and vector)

It looks to me like you''re using inheritance to effect aspects, not polymorphism. Since this isn''t what inheritance was meant for, there is some potential for unforseen side effects. (diamond inheritance, shadowing, etc)

Since you asked, I think it''s "bad," but I don''t know if there''s a better way to express the idiom. I think I myself would instead use a bunch of interfaces because C++ multiple inheritance has the potential to get very weird and as such frightens me terribly.

"Without deviation, progress itself is impossible." -- Frank Zappa

Share this post


Link to post
Share on other sites
Etnu - diamond inheritance is not a problem with member functions, but with member variables. Your code is simply ambiguous and will be flagged as so by your compiler - you''ll need to add a using statement to tell it which version to use and thus get it to compile.

The real problem with diamond inheritance is with data members, since both ClassA and ClassB bring in their own, independent copy of Class1''s member variables. ClassA member functions inherited in Class2 will work on ClassA''s copy of Class1, and similarly for ClassB.


class Class1
{
protected:
int foo;
public:
Class1() : foo(42) {}
virtual ~Class1() {}
};

class ClassA : public Class1
{
public:
void SetFoo(int i) { foo = i; }
};

class ClassB : public Class1
{
public:
int GetFoo() const { return foo; }
};

class Class2 : public ClassA, public ClassB {};


So, code like
int main()
{
Class2 obj;
obj.SetFoo(1000);
std::cout << obj.GetFoo() << std::endl;
}

will print 42 out (ClassB''s copy of foo) and not 1000 (ClassA''s copy of foo).

C++ answer to this problem is virtual inheritance, which will merge all the instances of the base classes so inherited.

That is the real issue with diamond inheritance.


“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”
— Brian W. Kernighan

Share this post


Link to post
Share on other sites
I very much agree with Etnu''s advice - you are not necessarily doing anything "wrong" but there may be other ways to accomplish what you are doing. It’s refreshing to see advice and comments given without the black and white holier than thou rhetoric.

A technique that may be of some use to you is the decorator pattern. Its typically used to avoid a phenomenon called class explosion. "The joy of patterns" has an example they use where they are assembling a cheeseburger and adding various toppings - and how the price gets computed. The problem you run into is that you could end up needing 4096 to cover 12 different characteristics (toppings in the example). In reality, the number will be less as you may not need every possible permutation but it’s very possible for things to get out of control. Using this type of technique could resolve some of the architectural issues that you are beginning to see, although there may be other more suitable solutions that offer other advantages. A quick google search (gamedev search too...) should yield a number of examples.

Another mechanism that I have seen used, which is far more complex, is in the Torque engine. The scripting engine allows you to add properties and functions somewhat dynamically into an object instance. At first I thought this was a very bad idea, if not confusing. However, after seeing how it can be used within the context of the scripting engine to build very specific objects I have to say it’s pretty clever as well as useful.


While I cannot say that both of these ideas are the Rosetta stone in your case, hopefully they offer some ideas for you to craft a solution that maximizes what you want your architecture to do and minimize complications in its maintenance and use.



#dth-0

Share this post


Link to post
Share on other sites
Yes, data members are the big issue.

You''re still adding way more overhead than you need to your classes though, and do you really want to have to put this thing in your code?


class CPlayer : public CUpdateObject, public CVisibleObject, public CMovingObject, public CLivingObject, public CBlahBlahblah
{
};


I''m not saying you shouldn''t use multiple inheritence at all - i''m just saying you''re probably overdoing it a bit, and will likely wind up with difficult and hard to maintain code. MOST of that stuff is closely related, and should be resolved through scripting rather than through complex multiple inheritence schemes. You''re coding for the "exception" and making the "rule" more complicated than it needs to be.

Share this post


Link to post
Share on other sites
quote:

Question to all: on composition, lets say i have a car with an engine:


class Engine {public: void ChangeOil();private: int oil_level;};class Car {public: void Drive(); void Stop(); void GasUp():Private: Engine* engine;};


And i want to ChangeOil() in the Car. Does car need to have a "wrapper" function to do that?


public: ChangeOil() { engine->ChangeOil(); }


Is that the "right" way to make composition? I hate wrapper functions! The alternative is to make the engine public and call it directly. I can see how people might frown on that, but you can see how it saves on useless wrapper functions!



That isn't composition, composition is basically object containment so taking your example, a car ins't a engine it "has a" engine e.g


class engine {
public:
engine();
bool add_fuel(float);
};

class car {
engine _engine; //<-- this is composition

public:
car(): _engine() {}
};


quote:

Some people believe that the purpose of inheritance is code reuse. In C++, this is wrong. Stated plainly, "inheritance is not for code reuse." The purpose of inheritance in C++ is to express interface compliance (subtyping), not to get code reuse.



What they are saying is to prefer type inheritance over class inheritance.

There are 2 types of inheritance super type/sub type and super class / sub class. super class / sub class inheritance is known as implementation inheritance where your trying to reuse code, super type / sub type inheritance is interface inheritance its about defining interfaces/protocols of interacting objects implementation is meaning less its the operations of the type that are important.

C++ supports both into one construct the class, a class defines a type and implementation. To achieve interface inheritance you use ABI classess that is a class with no data members and pure virutal member functions other types sub type from it. C++ support multiple interface & implementation inheritance.

Java supports this by sperating the 2 into interfaces and classess. Java doesn't support multiple implementation inheritance but supports multiple interface inheritance the reason for this is multiple implentation inheritance can get you in to all kinds of trouble because of data members, where as interface inheritance is much cleaner abstraction because your talking about types which you should be.

When Bjarne Stroustroup was designing C++ he had interface inheritance in mind, he said he was trying to get idea across to people but couldn't back then, he said that when came Java along it managed to get the point across well and it helped people to understand it more when they went back to C++.

When you create multiple interface inheritance your giving you object multiple roles/faces as UML describe it, its like an actor who can change his behaviour for each film, or someone who has more than one job thats not the same.

In a film you only care about the character, the role the actor is playing in context (just one interface/role of an actor), you don't care what the actor does outside of the context (implementation details). Your only interested in the characters behvaiour, simillar when you write a parameter of function you only interested in the behaviour of the object not how its implemented.

This causes module depandancies. This is also the same with what happens in implementation inheritance, you start to make module depandancies. All the time your trying to encapsulate your data members from the clients of a class, but you allow them to access it through inheritance, if you change you type's repsentation other clients that inherit are gonna have to re do they code.

Obviously depending on your type it may not make sense to encapsulate at all because its representation is concrete and nevers changes like mathetacial objects such vectors and points, these are concreate abstractions.

Don't worry thou you can really have your cake and eat it too. Most flexiable solution is start with interface inheritance to define protocols then implement thous intefaces with abstract class that you use for implementation inheritance (sparingly) to reuse code.

You should read these articles:

http://www2.unl.ac.uk/~11mulholland/im52P/rogers1.html

http://www2.unl.ac.uk/~11mulholland/im52P/rogers2.html

[edited by - snk_kid on June 11, 2004 5:02:53 AM]

Share this post


Link to post
Share on other sites
A real simple way to avoid name collisions when using multiple inheritance:

1) put the name of the class in the member functions and variable names
2) avoid multiple inheritance of anything but abstract classes
3) avoid inheritance heirarchies bigger than two levels; use aggregation & composition instead

If you design to an interface when designing your base (abstract) classes with the idea that they''ll be multiply inherited, you can consciously avoid name collisions.

The OP''s idea is a good one; combine the facets of the applications'' game models as several different interfaces. Use Observers that observe each particular model type.

The concrete Model class you create from several base classes has the advantage of being able to mix data from each model type without breaking encapsulation.

On a pragmatic level, it makes sense that a real-life Object can be several different, unrelated things... why not multiply inherit?


Chad

Share this post


Link to post
Share on other sites


As you can see, the ugly part is the whole collision detection scheme which forces diamond inheritance. It works, but is not ideal. I may get rid of half the diamond and find some other way of implementing that functionality. The problem is that the class structure is closely tied to my basic collision detection strategy which needs to be able to handle 1) multiple collision geometries and 2) multiple "types" like player, enemy, world, etc

However, all of this is the "setup". Everything in the game is a combination of these basic core classes. Once you get to the bottom of the list (where we find actually usable game objects), you don''t have to worry about all that jazz up top, it''s just a straight inheritance unless you want something more funky. For example, A Particle class may by a combonation of core classes, but if you want different kinds of particles, they just inherit from Particle instead of trying to do it from scratch.

Share this post


Link to post
Share on other sites
my suggestion would be to get rid of the whole collisionenemy/player/object interface. i can''t see that it''s necessary. you can get the same effects through collision IDs & messages. i belive you have that interface to handle things like enemy bullets hurt players, but not other enemies and stuff of that nature.

essentially what you can have is a collision matrix that defines collision types and with what other objects they collide


// 1 2 3 4
1 X

2 0 0

3 X 0 X

4 0 X X X


since effects are symmetric you only need to define one half of the matrix. but essentially X indicates "collides with" and 0 indicates "does not collide". So a bullet, when it collides, always does the same thing -> send a "Hit_by_bullet" message to the collidee. you then have each collidee implement a "Hit_by_bullet" message handler that applies damage. becase enemy bullets are transparent to enemies, they will never receive the "Hit_by_bullet" message except from a player bullet so you don''t need to bother with specific logic like

if bullet from player && i''m an enemy
....
else
....

this also offers a nice easily expandible situation. someday you might decide it''d be nice to have friendly fire. in that case you just change a 0 to an X rather than having to implement a new method in your enemyCollisionHandler routine.

as far as straight up collision detection it shouldn''t be different for different objects outside of some parameters that should be abstracted to the collisionObject base class (elasticity, mass, velocity, collision volume shape, etc). the physics of collision is straight forward and can be nicely handled by a single base class & physics system. the physics system should obviously reference the collision matrix and ignore collisions between objects with a 0 in their matrix field. outside of that, you just have the physics system call some virtual method that you will extend to pump out your collision messages.

anyway, this is perhaps offtopic, but i''m trying to go for the "is this bad" aspect in a more concrete way.

-me

Share this post


Link to post
Share on other sites
Yes i understand the getting rid of the player/enemy/world collision objects. That's my next project in fact.

The collision matrix is actually what i have now. But i actually have 2 matrices: one for geometry, one for object type. I do this because i don't even want to check collision between objects for which it makes no sens. Bullets ought not to collide with other bullets. So i don't define any function for that. (the matrix is a table of function pointers). If there is a function for it, i use it. The function then calls a virtual function in each object, like so (cut and paste example):

void IntersectTestManager::IntersectPlayerWorldWrapper( CollisionObject* A, CollisionObject* B ) {
CollisionObjectPlayer* objA = dynamic_cast<CollisionObjectPlayer*>(A);
CollisionObjectWorld* objB = dynamic_cast<CollisionObjectWorld*>(B);
if (objA) { objA->IntersectWorld(objB); }
if (objB) { objB->IntersectPlayer(objA); }
}


The nice thing about this system is that i can inherit the collision functionality but since it's all through virtual functions, i can define it in a base class and reimplement it downstream if i want.

For a good read on how i came up with this system and a lot of source code, check out this thread

[edited by - leiavoia on June 11, 2004 2:00:03 PM]

Share this post


Link to post
Share on other sites
If inheritance was not for code reuse and only for typing, then inheritence would not include all the functional code that you included in your parent class, only the function headers. Instead of "extends" there would only be the java style concept of "interfaces" that are "implemented".

Ineritence != polymorphism. Both are good features that are useful for different things. Inheritence is a time savor and readability aid, wheras polymorphism is the powerful functional tool that the original quote refers to. Both must be used carefully.

This is also why I think bashing multiple inheritence is a crock. If you use multiple inheritence purely for polymorphic purposes (instead of for code-reuse purposes) it runs very neatly. Only if you use it for code-reuse do you start getting into trouble, which is I think the point of this complaint. In other words:

"All broad, sweeping generalisations are false."

Share this post


Link to post
Share on other sites
Nobody stated that inheritence shouldn''t be done for code reuse - we just stated that it''s not it''s PRIMARY purpose; polymorphism is.

Without polymorphism, there is no point in using a class-based heirarchy in the first place, as it has no tangible advantage over a functional paradigm.

Share this post


Link to post
Share on other sites
quote:
Original post by Pxtl
If inheritance was not for code reuse and only for typing, then inheritence would not include all the functional code that you included in your parent class, only the function headers.
It doesn''t. Only virtual functions and protected members are inherited, which would seem to suggest that it''s an escape to provide some reuse as opposed to a mechanism intended fundamentally for effecting code reuse.

quote:
This is also why I think bashing multiple inheritence is a crock. If you use multiple inheritence purely for polymorphic purposes (instead of for code-reuse purposes) it runs very neatly.
Which is exactly the point of the MI-bashers, and the argument in favor of interfaces.

Disestablishmentarianism. So sad, so tragic.

Share this post


Link to post
Share on other sites
I don''t even really like *single* inheritance, let alone multiple inheritance. I tend to be more comfortable with object composition, and I wish there were an Algol-syntaxed language which could do more of the delegation for you automatically (I hate the wrapper functions too). It''s one of the things I have in mind for Defn.

Anyway, MI can be a useful tool, but it''s quite unnecessary (in the way that basically everything else in a HLL is unnecessary). I''m personally quite used to the Java way of doing things (where you implement multiple interfaces, and supposedly miss out on the code reuse; but you can always use composition as a "back-end" for implementing those interfaces). Inheritance and delegation are to a large extent equivalent, and I''m more comfortable thinking of things in terms of delegation, generally speaking. YMMV.

That said:

quote:
Original post by leiavoia
I have a "player". A player is a
- UpdateObject (used to call object->Update() every frame)
- CollisionObject (of course)
- LifeObject (has hit points and can die)
- PhysicalObject (has a location, speed, weight, and vector)

So what i do is program each of these classes seperately. Then i patch them all together to create a "Player" class like so:

class Player: public UpdateObject, public CollisionObject, public LifeObject, public PhysicalObject {
// override functions and whatnot
}




I understand that a physical object may or may not be able to collide with things, but something which collides needs physical properties. I''d probably end up with something like:


class Object { /* anything that can ->Update(). */ }
class LivingObject : public Object {
/* if it has hit points, shouldn''t it necessarily update each frame? */
}
class PhysicalRepresentation {
/* a set of measurements for an object */
}
class CollidablePhysicalRepresentation : public PhysicalRepresentation {
/* adds stuff needed for collision detection */
}
class Player : public LivingObject {
CollidablePhysicalRepresenation cpr; /* hee hee */
}


You can see perhaps how that will extend to your full structure. Although I think you will have to end up implementing some kind of multiple dispatch for collision :/

Share this post


Link to post
Share on other sites
quote:
Original post by Zahlman
I'm personally quite used to the Java way of doing things (where you implement multiple interfaces, and supposedly miss out on the code reuse



Thats not true, you can utilise interface & implementation inheritance together, you get a clean abstraction & code reuse.

This is done with java inteface that declares a type, an abstract class implements the type/interface and provides an extension point of code reuse by making concreate classes derive from it, then you use/store interface pointers for dynamic polymorphism. It is done alot in the java libraries.

Take a look the collections library for alot examples. List is a interface type, they provide an abstract class called AbstractList that implements the interfaces provides an extension point for code reuse and that is without module coupling that people keep doing by making there data members protected.

[edited by - snk_kid on June 11, 2004 8:09:53 PM]

Share this post


Link to post
Share on other sites
Alternately, use a dynamically typed language like Python, where interface implementation is implicit. That is, if you make three classes with the same methods that expect the same arguments, then they''re already interchangeable.

Plus, because classes themselves in Python are objects, then making templates is trivial. That''s always a feature I would''ve loved in C++ (treat classes as objects, and thus you could make classes implement an interface, and thus design your template class around this interface).

Share this post


Link to post
Share on other sites
quote:

I don''t even really like *single* inheritance, let alone multiple inheritance.



Then what tangible advantage does OOP offer over functional paradigms? You get a slightly more "logical" layout to your program, but you''re really not doing anything that couldn''t be done (and possibly done easier) in a functional language.

Share this post


Link to post
Share on other sites

  • Advertisement