Skip to main content
gamedev.net
🔒 Locked

Question about multiple inheritance

Started by stodge 14 replies 200+ views
Original Post
stodge
stodge
Ok, I have three classes:
    
class A
{
public: 
   virtual void Update() = 0;
};

class B
{
public:
   virtual void Edit() = 0;
};

class C: public A, public B
{
public: 
   void Update();
   void Edit();
};
  

I create an instance of C and cast it to B:

      
C* c = new C();
B* b = (B*)c;
b->Edit();
    
Something isn't right (probably me!) but the wrong function gets called. Instead of calling Edit, Update gets called. Any ideas why this might be? I know I probbaly shouldn't be using multiple inheritance, but I'm using it in a Java-interface like fashion. Thanks [edited by - stodge on December 19, 2002 12:22:54 AM]
---------------------http://www.stodge.net
Dean Harding
Dean Harding
Your Edit and Update methods in A and B aren''t declared virtual. I''m kind of surprised that''s happening, actually. What would normally happen is B::Edit() would be called...

If I had my way, I''d have all of you shot!


codeka.com - Just click it.

stodge
stodge
Dean, I''m glad you mentioned that. I forgot that when I posted this topic, so I went back and updated the code to show what I really have!

Thanks
---------------------http://www.stodge.net
stodge
stodge
You're right, I don't know what I'm doing, but I'm having fun learning!


    B* b = dynamic_cast<B*>(c);    


This works for me.

[edited by - stodge on December 19, 2002 12:32:16 AM]
---------------------http://www.stodge.net
Qw3r7yU10p!
Qw3r7yU10p!
you don''t have to cast anything

a pointer to a C is already a pointer to a B because C is a B (you said it is, so it is)

C* c = new C;
B* b = c;
b->Edit();
SabreMan
SabreMan
quote:
Original post by stodge
Something isn''t right (probably me!) but the wrong function gets called. Instead of calling Edit, Update gets called.

Are you sure about that? If it does, then your compiler''s very broken. The only thing I can see wrong with your example is as petewood pointed out - you don''t need to cast up the hierarchy, casting to a public base is done implicitly by the compiler. That won''t cause your problem though.
quote:

I know I probbaly shouldn''t be using multiple inheritance, but I''m using it in a Java-interface like fashion.

There''s nothing particularly wrong with using MI if it''s The Right Thing for the problem.
LilBudyWizer
LilBudyWizer
It is pretty much what I would expect. I''ve never seen a explaination of exactly how a class inheriting from multiple polymorphic bases resolves the problem of there being two vtable pointers at the start of the data storage. How does a function in the base class know which vtable to use or for that matter that there is a second vtable? With single inheritance it is easy enough. All the offsets for the base class are the same. For that matter how does a function in a non-polymorphic base class know the offsets for its data members?

One work around is to make the base classes have two vtables by inheriting from two base classes. That would give you four vtables in the derived class. If they are the same base classes through and inherited virtual then there are only two vtable pointers in the derived class. That may get it to call the right functions, but it is still questionable if that function will then refer to the right data.
Keys to success: Ability, ambition and opportunity.
LilBudyWizer
LilBudyWizer
Hehe, I don''t really need an explaination since I have a compiler. First thing it isn''t just the vtable, but rather the entire class, i.e. vtable for the first class, the data members for the first class, vtable for the second class, the data members for the second class, the data members of the derived class.

The derived class is considered to be a derivative of the first base class, but not the second. So an explicit cast is needed. That cast does not just copy the pointer, but actually calculates the address at which the vtable and data members for the second base class starts at. Apparently that is a bug in Stodge''s compiler. It is just copying the pointer rather than actually casting it to the correct offset. So about the only work around would be either what I suggested if it works or casting it down to a char pointer, adding the correct value and casting it back up. So perhaps (B *)(((char *)c)+sizeof(A)). Perhaps not pretty, but at least it allows you to do a correct cast.
Keys to success: Ability, ambition and opportunity.
Qw3r7yU10p!
Qw3r7yU10p!
quote:
Original post by davidsporn
Here is an article related with what you experienced :
Conversations: Roots (opens in a new window)



thanks for that david, i''d forgotten about that article and it''s sorted out a problem for me exactly as it described.
Stoffel
Stoffel
I believe the reason why there was the incorrect behavior is that the C-style cast is interpretted as a reinterpret_cast, and therefore the compiler slices the C object when casting to a B. I'd prefer no cast at all, but dynamic_cast is the correct thing to do if you would like the implicit casting to be explicit.

Language lawyers, set me straight if I misspoke.

[edited by - Stoffel on December 30, 2002 3:41:02 PM]
Qw3r7yU10p!
Qw3r7yU10p!
no cast needed.

i don''t think using the explicit cast goes toward making code clearer.

but if you do use dynamic cast to show what you''re doing (even though it''s not necessary), you should check the return value isn''t zero. this is good practice even though you know C is a B.

stoffel: i believe you may be correct about the slicing. it''s undefined behaviour so it could sometimes work, sometimes not.

what compiler are you using stodge? does it work okay if you don''t use the c-style cast?
Stoffel
Stoffel
Wow, I totally misspoke about dynamic_cast--that''s the absolute wrong thing to do here (teach me to type after coming back from a 1-week vacation). dynamic_cast should be used only in concert with RTTI, e.g. you''re testing if a base-class pointer can be converted to a derived class. static_cast is the correct cast to use, not dynamic_cast.

PS: I''ve tried duplicating this behavior on my gcc and I can''t get it to happen. I''m not a gcc expert. I can reproduce the slicing error to work with reinterpret_cast, but not C-style cast. Here''s my program:

  
#include <stdio.h>

class IGo
{
public:
virtual void Go () = 0;
};

class IStop
{
public:
virtual void Stop () = 0;
};

class Car : public IGo, public IStop
{
public:
void Go () { printf ("Going...\n"); }
void Stop () { printf ("Stopping...\n"); }
};

int main ()
{
Car c;
IGo *gi;
IStop *si;
gi = (IGo*) &c
si = (IStop*) &c
gi->Go ();
si->Stop ();
return 0;
}

(Don''t bag on my for my use of stdio--apparently my unix account isn''t set-up to link with iostream, so I had to fall back)
Shannon Barber
Shannon Barber
The orginal code ought to work with either a static & dynamic cast, even if RTTI is disabled. If RTTI is required for the cast, the compiler ought to tell you if RTTI is disabled - RTTI is not required for this particular cast though.

If you do not specify the type of cast, it assumes some default - which must be reinterpret on stodge''s compiler/project.


  
class C: public A, public B
{
public:
void Update() {cout << "Update" << endl;}
void Edit() {cout << "Edit" << endl;}
};

int main()
{
C* c = new C();
reinterpret_cast<B*>(c)->Edit();
static_cast<B*>(c)->Edit();
dynamic_cast<B*>(c)->Edit();
}


Output:
quote:

Update
Edit
Edit



The reinterpret_cast engenders undefined behavior. We expect it to dereference the wrong vtbl, and it does. Add another function to B, and call it instead... who knows what will happen, it could format your hard-drive (most likely it will crash).
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.