Topic: Multple inheritance and virtual functions


Author: James Kanze <james-albert.kanze@vx.cit.alcatel.fr>
Date: 1997/01/03
Raw View
jlilley@empathy.com (John Lilley) writes:

|>  Guangliang He wrote:
|>  >
|>  > Can anyone explain to me the reason for different output of
|>  > the following program with different declaration of constructor
|>  > child::child(void *) vs child::child(baseA *)?
|>  > Using void * as the argument, I get what I expected: B::f().
|>  > But using baseA * as the argument, I get A::f() as the output.
|>
|>  > class baseA { public: virtual void g() = 0; };
|>  > class baseB { public: virtual void f() = 0; };
|>  >
|>  > class child {
|>  >   void *parent;
|>  > public:
|>  >   // using this constructor, the output is "B::f()"
|>  >   // child(void *a) : parent(a) {}
|>  >
|>  >   // using this constructor, the output is "A::g()"
|>  >   // child(baseA *a) : parent(a) {}
|>  >   void *GetParent() { return parent; }
|>  > };
|>  >
|>  > class derived : public baseB, public baseA {
|>  > public:
|>  >   void g() { cerr << "A::g()" << endl; }
|>  >   void f() { cerr << "B::f()" << endl; }
|>  > };
|>  >
|>  > int main()
|>  > {
|>  >   derived *cptr = new derived;
|>  >   child *childPtr = new child(cptr);
|>  >   baseB *bPtr = (baseB *) childPtr->GetParent();
|>  >   bPtr->f();
|>  > }
|>
|>  Let's consider the fundamental difference.  If this is declared:
|>     child(baseA *a) : parent(a) {}
|>  Then there is a cast  from derived* to baseA* and then to void*.  But if
|>  this is declared:
|>     child(void *a) : parent(a) {}
|>  Then there is a cast from derived* to void*
|>
|>  Both of these uses are evil and wrong ;-)

I cannot believe that I forgot to say this in my own answer: rewrite the
above code to use the new style casts.  You will find that you need to
use reinterpret_cast's (rather than static_cast's) to do so.  This is
the ultimate indication that what you are doing is "evil and wrong";
reinterpret_cast's do not make the necessary corrections for the code to
work.

--
James Kanze         home:     kanze@gabi-soft.fr        +33 (0)3 88 14 49 00
                    office:   kanze@vx.cit.alcatel.fr   +33 (0)1 69 63 14 54
GABI Software, Sarl., 8 rue des Francs Bourgeois, F-67000 Strasbourg, France
       -- Conseils en informatique industrielle --
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Guangliang He <ghe@superlink.net>
Date: 1996/12/30
Raw View
Can anyone explain to me the reason for different output of
the following program with different declaration of constructor
child::child(void *) vs child::child(baseA *)?
Using void * as the argument, I get what I expected: B::f().
But using baseA * as the argument, I get A::f() as the output.
Why do they differ? In my believe, all the constructor does is
to initialize the parent pointer. It shouldn't care what type
of the pointer passed in, it should only care about the address
of the pointer. The behavier here is certainly weired to me.
What does the C++ standard say about this?

This is compiled using g++ 2.7.2 on Linux 2.0.25 on a Pentium
machine. The compile command is 'g++ a.C -o a'

--- Code begins "a.C" ---
#include <iostream.h>

class baseA {
public:
  virtual void g() = 0;
};

class baseB {
public:
  virtual void f() = 0;
};

class child {
  void *parent;
public:
#if 0
  // using this constructor, the output of the main function
  // is "B::f()"
  child(void *a) : parent(a) {}
#else
  // using this constructor, the output of the main function
  // is "A::g()"
  child(baseA *a) : parent(a) {}
#endif

  void *GetParent() { return parent; }

};

class derived : public baseB, public baseA {
public:
  void g() { cerr << "A::g()" << endl; }
  void f() { cerr << "B::f()" << endl; }
};

int main()
{
  derived *cptr = new derived;

  child *childPtr = new child(cptr);

  baseB *bPtr = (baseB *) childPtr->GetParent();
  bPtr->f();

  return 0;
}

--- Code ends "a.C" ---

--
Guangliang He
mailto:ghe@superlink.net
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: jlilley@empathy.com (John Lilley)
Date: 1997/01/02
Raw View
Guangliang He wrote:
>
> Can anyone explain to me the reason for different output of
> the following program with different declaration of constructor
> child::child(void *) vs child::child(baseA *)?
> Using void * as the argument, I get what I expected: B::f().
> But using baseA * as the argument, I get A::f() as the output.

> class baseA { public: virtual void g() = 0; };
> class baseB { public: virtual void f() = 0; };
>
> class child {
>   void *parent;
> public:
>   // using this constructor, the output is "B::f()"
>   // child(void *a) : parent(a) {}
>
>   // using this constructor, the output is "A::g()"
>   // child(baseA *a) : parent(a) {}
>   void *GetParent() { return parent; }
> };
>
> class derived : public baseB, public baseA {
> public:
>   void g() { cerr << "A::g()" << endl; }
>   void f() { cerr << "B::f()" << endl; }
> };
>
> int main()
> {
>   derived *cptr = new derived;
>   child *childPtr = new child(cptr);
>   baseB *bPtr = (baseB *) childPtr->GetParent();
>   bPtr->f();
> }

Let's consider the fundamental difference.  If this is declared:
   child(baseA *a) : parent(a) {}
Then there is a cast  from derived* to baseA* and then to void*.  But if
this is declared:
   child(void *a) : parent(a) {}
Then there is a cast from derived* to void*

Both of these uses are evil and wrong ;-)

Consider the first case.  The cast from derived* to baseA* takes the
address of the baseA component of derived, which is *not* the same as
the address of derived. Given the baseA* you could successfully cast
back to derived*.  However, if you cast the baseA* to void* you lose the
information that the compiler needs to get back to derived*.  And you
don't even cast back to derived*, but to baseB*, which has no relation
whatsoever to baseA*.  So later, when you cast from void* to baseB*, the
compiler just converts the address to a baseB*, which is totally
unrelated to the address of the baseB component of the original derived
object.  So you now have a baseB* that does not point to a baseB.  This
is very, very, bad.  You are lucky that it did something observable
(e.g., invoking the g() method of baseA) instead of just crashing.

Now consider the second case.  You cast directly from derived* to void*,
which just copies the address.  Then you cast back from void* to
baseB*.  Since baseB is the first base class of derived, you lucked out
because the address of derived happens to be the same as the address of
the baseB component of derived.  So when you cast back to baseB* you get
the right thing.

Suggestion: if the parent is always used as a baseB*, just declare it as
baseB*, get rid of all the casting, and have a single constructor for
child:

   class child {
      baseB *parent;
   public:
      child(baseB *b) : parent(b) {}
      baseB *GetParent() { return parent; }
    };

If parent is *not* always used as a baseB*, then revisit your design --
you have an unclear picture of your object relationships.  In general,
lots of casting indicates that something is wrong.  The only common
exception is storing pointers to a base class and then "downcasting" to
the real class -- but even this must be approached with great care.

john lilley
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]