Jump to content
  • Advertisement

Archived

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

leiavoia

On Multiple Inheritance and Design Issues (controversy!)

This topic is 5182 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

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!