[comp.lang.c++] MI and virtual functions

kurtl@fai.UUCP (Kurt Luoto) (09/26/89)

I posted this to the gnu.g++ group, but didn't get any response.
I am assuming that for some reason it didn't get through,
or perhaps I need to reach the more general audience of comp.lang.c++.
I apologize for any redundancy.

I have been reading Lippman's "C++ Primer" to familiarize myself with
the features of C++ 2.0.  On page 312, in discussing function name
ambiguities that can arise from multiple inheritance, he gives an example
and states:

    "When the inherited members that share the same name are all
     member functions, it is therefore a good design strategy to
     define a derived class member function of the same name."

I wanted to try this out myself and see how it worked with virtual
functions.  I do not yet have AT&T C++ 2.0 available to me, but I do
have GNU C++.  Here follows my test program, a minor variation, and
their respective outputs.  I found the results unintuitive.  I would
like to know whether this is a feature, bug, undefined area of the
language, or whatever.

--------------------- EXAMPLE -------------------------
% g++ -v
g++ version 1.35.1-
%
% cat test1.C

#include <stream.h>

class A {
    int  a ;
  public:
    A();
    virtual int foo();
    };

A::A() { a = 0 ; }

int A::foo() {
    ++a;
    cout << "A::foo() : a = " << a << "\n" ;
    return a ;
    }

class B {
    int  b ;
  public:
    B();
    virtual int foo();
    };

B::B() { b = 0 ; }

int B::foo() {
    ++b;
    cout << "B::foo() : b = " << b << "\n" ;
    return b ;
    }

class C : public A, public B {
    int  c ;
  public:
    C();
    int foo();
    };

C::C() { c = 0 ; }

int C::foo() {
    A::foo();
    B::foo();
    ++c;
    cout << "C::foo() : c = " << c << "\n" ;
    return c ;
    }

int garpA( A* a ) {
    cout << "garpA:\n" ;
    return a->foo();
    }

int garpB( B* b ) {
    cout << "garpB:\n" ;
    return b->foo();
    }

int garpC( C* c ) {
    cout << "garpC:\n" ;
    return c->foo();
    }

main( ) {
    C  c ;
    garpA( &c );
    garpB( &c );
    garpC( &c );
    exit(0);
    }
%
% g++ -o test1 test1.C
% test1
garpA:
A::foo() : a = 1
B::foo() : b = 1
C::foo() : c = 1
garpB:
B::foo() : b = 2
garpC:
A::foo() : a = 2
B::foo() : b = 3
C::foo() : c = 2
%
% diff test1.C test2.C
34c34
< class C : public A, public B {
---
> class C : public B, public A {
%
% g++ -o test2 test2.C
% test2
garpA:
A::foo() : a = 1
garpB:
A::foo() : a = 2
B::foo() : b = 1
C::foo() : c = 1
garpC:
A::foo() : a = 3
B::foo() : b = 2
C::foo() : c = 2
--------------------- END OF EXAMPLE -------------------------

I can see by the output how the compiler implementation behaves
when the order of base classes is changed for the derived class C.
To me it is rather non-intuitive that the results should differ
based simply on this ordering.  Is this a feature of 2.0?  Does the
(hypothetical) language standard say that behaviour in this case is
undefined?  Lippman's book gives no indication that I can see.
Does the AT&T 2.0 compiler work the same way as GNU C++?

Behaviour that I would have expected would be one of the following:

    1. The compiler would flag that the virtual function C::foo() is
       not allowable and produce a compile time error.

    2. The compiler would give a warning message that accessing foo()
       via different base-class type pointers (A*, B*, C*) to an
       instance of C may produce unexpected results.

    3. The compiler would create virtual functions and tables for both
       the A and B parts of C such that accessing foo() through any type
       of pointer to a C instance (A*, B*, or C*) would all produce the
       same behaviour.

Could someone please enlighten me?
I apologize if this question has already been thrashed out before.
-----------------
Kurt W. Luoto
I'm unsure of my mailer. Try  kurtl@fai.fai.com  or  ...!sun!fai!kurtl

shopiro@alice.UUCP (Jonathan Shopiro) (09/28/89)

In article <2534@fai.UUCP>, kurtl@fai.UUCP (Kurt Luoto) writes:
> I have been reading Lippman's "C++ Primer" to familiarize myself with
> the features of C++ 2.0.  On page 312, in discussing function name
> ambiguities that can arise from multiple inheritance, he gives an example
> and states:
> 
>     "When the inherited members that share the same name are all
>      member functions, it is therefore a good design strategy to
>      define a derived class member function of the same name."

In fact, if they are virtual functions, you _must_ declare a function
by that name in the derived class.
> 
> I wanted to try this out myself and see how it worked with virtual
> functions.  I do not yet have AT&T C++ 2.0 available to me, but I do
> have GNU C++.
Reversing the order of the base classes has no effect on the output of
your program in AT&T C++ 2.0.

As a simple example of how the language should behave:

struct A {
	virtual void	foo();
};
struct B {
	virtual void	foo();
};
struct C : A, B {
	virtual void	foo();
};
void
bar()
{
	C	c;
	A*	ap = &c;
	B*	bp = &c;
	C*	cp = &c;
	// these calls all do the same thing
	ap->foo();	// calls C::foo()
	bp->foo();	// calls C::foo()
	cp->foo();	// calls C::foo()
}

-- 
		Jonathan Shopiro
		AT&T Bell Laboratories, Warren, NJ  07060-0908
		research!shopiro   (201) 580-4229