[comp.lang.c++] help! with multiple inheritance

spolsky-joel@CS.YALE.EDU (Joel Spolsky) (12/18/90)

Hi, Perhaps some kind soul can tell me why this program fragment
doesn't work?  I am trying to use a base pointer and get the correct
derived method, but this doesn't work when the base pointer has been
cast to a void * and then back to the base pointer. 

[Why, you may ask, do I need to cast this through a void pointer?
well, because I am using InterViews, and I am using InterViews
"Pictures" to store things, and when I get them back they have been
cast into Graphic *'s, so I have to cast them back, but by then the
derived methods don't work. Arg!]

(I'm using g++ version 1.37.1 for Sun SPARC station, Sun OS 4.0)

#include "std.h"

class a {int x;};
class b {float x,y;};
class Tiger {
  public:
    virtual void Roar (void)
	{printf("roar\n");}
};

class Bengal: public a, public Tiger{
  public:
    virtual void Roar (void)
	{printf("bengal roar\n");}
};

class Senegal: public b, public Tiger{
  public:
    virtual void Roar (void)
	{printf("senegal roar\n");}
};


main()
{
    Senegal *ps = new Senegal;


//  This works, printing "senegal roar":
    printf("cast to Tiger *:\n");
    ((Tiger *) ps)->Roar();


//  This segment faults:
    printf("cast thru:\n");
    ((Tiger *) (void *) ps)->Roar();

}


Note: There is no problem if Bengal and Senegal do _not_ multiple
inherit.

Thanks in advance for your help,

--
Joel Spolsky          // And these streets, Quiet as a sleeping army
spolsky@cs.yale.edu   // Send their battered dreams to heaven.   _Paul Simon

kaiser@ananke.stgt.sub.org (Andreas Kaiser) (12/19/90)

In a message of <Dec 18 06:50>, Joel Spolsky (spolsky-joel@CS.YALE.EDU ) writes: 
 
 JS> class Tiger {
 JS>   public:
 JS>     virtual void Roar (void)
 JS>         {printf("roar\n");}
 JS> };

 JS> class Senegal: public b, public Tiger{
 JS>   public:
 JS>     virtual void Roar (void)
 JS>         {printf("senegal roar\n");}
 JS> };

Consider the implementation of Senegal: Since it has two base classes, at least 
one of those has a non-zero offset within the derived class. Since typically the 
first has zero offset (but don't rely on this fact, sinceimplementations may 
vary), the base class Tiger has not.

 JS> main()
 JS> {
 JS>     Senegal *ps = new Senegal;

 JS> //  This works, printing "senegal roar":
 JS>     printf("cast to Tiger *:\n");
 JS>     ((Tiger *) ps)->Roar();

The compiler recognizes that Tiger is a base class of your object of type 
Senegal and converts the pointer by adding the offset mentioned above.

 JS> //  This segment faults:
 JS>     printf("cast thru:\n");
 JS>     ((Tiger *) (void *) ps)->Roar();

But now the compiler does not know anything aboid the pointer, since it is a 
void pointer. So the cast is in effect s noop and does not add or subtract a 
constant.

Try
        printf ("%p %p\n", (Tiger *) ps, (Tiger *) (void *) ps);
and you'll see the difference.

In the case of single inheritance, the offset typically is zero, so both are the 
same and program works.

But you hit a point. This is a real problem in C++. Until the day when templates 
are implemented in most available compilers, void pointers have to be used in 
some circumstances. Maybe this problem is the reason, why Bjarne Stroustup left 
the implicit conversion TO (void *), but removed the implicit conversion FROM 
(void *) from the language. Type casts are not necessarily transitive 
operations.

Hint: replace
          (Tiger *) (void *) ps
by
          (Tiger *) (void *) (Tiger *) ps

- If you have a program, where a (void *) is converted to (A *) before use, 
always carry out the conversion to (A *) BEFORE converting to (void *).
- Always make sure, that the conversion to and from some pointer type converts 
between exactly the same types.
- Do not allow a cast to and from some type to carry out an implicit third 
conversion (in your example: (Tiger *) ps).

                Andreas
 
 

mat@mole-end.UUCP (Mark A Terribile) (12/21/90)

> Hi, Perhaps some kind soul can tell me why this program fragment
> doesn't work?  I am trying to use a base pointer and get the correct
> derived method, but this doesn't work when the base pointer has been
> cast to a void * and then back to the base pointer. 

> Note: There is no problem if Bengal and Senegal do _not_ multiple
> inherit.

	Given base class B and derived class D, these two code
	fragments are NOT equivalent.

		B* bp = ...
		D* dp = (D*)bp;


		B* bp = ...
		D* dp = (D*)(void*)bp;

	They are NOT equivalent.  They are NOT equivalent.  They are NOT
	equivalent.  Nor are they equivalent.

Why not?  Because if B is a base class (direct or indirect) of D, the
conversion between B* and D* may involve adding or subracting an offset.
Conversion to and from void*, on the other hand, preserves the bit pattern
(does not add or subtract any offset ...)

There's a good deal of magic going on behind the scenes here (and in other
places as well).  You can play games, but there are certain rules you must
obey.  One is that if you have to store a pointer as a void*, you must first
retrieve it as it was stored.  This may mean that you have to store it
according to how you mean to retrieve it.  If it will be retrieved as a
pointer-to-base, you must first cast it to pointer-to-base and THEN to
void* .
-- 

 (This man's opinions are his own.)
 From mole-end				Mark Terribile