3

I'm seeing very different behavior in how C++ and Python handle the diamond inheritance scenario.

For the following two code snippets, the logic is the same but the output is different. I don't understand why. I'd really appreciate it if someone could offer some insight.

Python prints out "B B"

class A(object):
    def foo(self):
        print 'A'

class B(A):
    def foo(self):
        print 'B'
    def bar(self):
        self.foo()

class C(A):
    def foo(self):
        print 'C'
    def bar(self):
        self.foo()

class D(B, C):
    def bar(self):
        B.bar(self)
        C.bar(self)

D().bar()

But C++ prints out "B C"

struct A
{   
    virtual void foo() {
        cout << "A" << endl;
    }   
};  

struct B : public A
{   
    virtual void foo() {
        cout << "B" << endl;
    }   

    virtual void bar() { this->foo(); }
};  

struct C : public A
{   
    virtual void foo() {
        cout << "C" << endl;
    }   

    virtual void bar() { this->foo(); }
};  

struct D : public B, public C
{   
    virtual void bar() {
        B::bar();
        C::bar();
    }   
};  

int main()
{   
    D().bar();
} 

When I add the line cout << typeid(*this).name() << endl; to every bar() method above in C++, this is what gets printed out:

B 0x7fff803d9b70
C 0x7fff803d9b78
D 0x7fff803d9b70

It seems that B and D share the same address, but C is in a different vtable.

Edit

As suggested in the reply @JoranBeasley, I tried the following checks for the python code:

D.foo == B.foo
D.foo == C.foo
D.foo is B.foo
D.foo is C.foo

Python 2.7 prints out True False False False

Python 3.4 prints out True False True False

It seems that both B.foo and D.foo are the same method by value in both python 2 and 3, but python 2 makes a copy while python 3 doesn't.

Ainz Titor
  • 1,497
  • 1
  • 15
  • 22
  • If you want python-like inheritance in C++, you need to use virtual inheritance. – o11c Jul 01 '15 at 00:56
  • 2
    This C++ is not a real diamond: `D` has 2 different inherited `A` bases. – rodrigo Jul 01 '15 at 00:58
  • @o11c: I actually want the other way around: C++ behavior is preferred. – Ainz Titor Jul 01 '15 at 00:59
  • It is a good question. You can read a few points from this http://stackoverflow.com/questions/12457603/python-multiple-inheritance-avoid-calling-the-constructors-twice-in-diamond-sha – Bhargav Rao Jul 01 '15 at 01:05
  • @BhargavRao that's a really good post. Thx! – Ainz Titor Jul 01 '15 at 01:08
  • @JimJarvis That is not truly possible in Python unless you use metaclass hackery to generate copies of base classes, which will likely confuse *everyone* and break all sorts of things. – o11c Jul 01 '15 at 01:09
  • good question. I would have expected B,C as in C++. Good to learn these things about python, and thanks to @Joran Beasley's answer – tenwest Jul 01 '15 at 01:14

1 Answers1

4

I can only explain the python bits ...

C.bar(self)

passes the instance of D that you created in as self

so when it calls self.foo() it is no different than if you had said self.foo() inside of D.bar (i.e self remains staticly the same thing)

since inheritance is done Right to Left B.foo shadows C.foo

D.foo is B.foo # should print True
D.foo is C.foo # should print false

you could of coarse always call

C.foo(self) #prints C as expected

this really has nothing to do with Python multiple inheritance method resolution order since that is handled with the super() call ... instead you are trying to explicitly call your parent class methods, rather than allowing python to resolve them for you implicitly

as far as why its different than C++ , its because it is two seperate languages that implemented inheritance and multiple inheritance in distinct ways...

as mentioned in the comments you could change D's definition to

class D(C,B):
     ...

then D().foo() will print C

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • 1
    Also mention that if he changes the order of inheritance (`class D(B, C):`) to (`class D(C, B):`) it will print `C C` ;) (after you have explained right-to-left inheritance) – Bhargav Rao Jul 01 '15 at 00:54
  • Thanks for the answer. But both lines print out `False`. – Ainz Titor Jul 01 '15 at 00:58
  • @JimJarvis Could you please mention the Python version? – Bhargav Rao Jul 01 '15 at 00:59
  • @BhargavRao python 2.7 – Ainz Titor Jul 01 '15 at 01:01
  • 1
    @JimJarvis Aha. That is the reason. Joran's answer is in Python3. – Bhargav Rao Jul 01 '15 at 01:01
  • hmmm you are right ... I will look into that when I get home (Im actually on 2.6.6) thats what I get for not testing it ... i will figure it out – Joran Beasley Jul 01 '15 at 01:04
  • 1
    im sure its something about inheriting for `object` in python3 it is automagic ... in python2 it has to be explicit – Joran Beasley Jul 01 '15 at 01:06
  • well now im really confused and starting to doubt myself and my understanding ... I cant get them to evaluate to the same no matter what in python2 ... oh well Im still sure that this is how it works :P – Joran Beasley Jul 01 '15 at 01:08
  • @JoranBeasley: python 3's behavior makes more sense. B.foo should be the same as D.foo – Ainz Titor Jul 01 '15 at 01:10
  • @JimJarvis Want to see more fun. Remove the `foo` function completely from class `B` and remove the inheritance of `object` from `A`. Then execute the code. It will yield more surprising results. – Bhargav Rao Jul 01 '15 at 01:17
  • @BhargavRao it prints out "A A" ... and python 3 prints out "C C". I've heard about this python object mechanism change, but never see it in action. – Ainz Titor Jul 01 '15 at 01:33