Question

I came across this when I was experimenting with diamond problem in C++. The following program works in Visual studio 2010 for me and I get the following out put.

D
D
A
A
B
Error7
C
C
5
7
7
Press any key to continue . . .

Please explain this to me, if you know any explanation.

Note that, Any of B object is never constructed in any manner. Most derived class D gets derived from C and A only. Not the B. I am wondering where is this m_i2 getting value 7 from?

#include "stdafx.h"
#include <iostream>
using namespace std;
class A{ int m_i1;
public:
    A(int i1){ m_i1 = i1;}  
    int GetMI1(){ return m_i1;} 
    void printABCD(){cout << "A" << endl;}
};
class B : public   A {
    int m_i2;
public:
    B(int i1):A(i1){} 
    void printABCD(){cout << "B" << endl;}
    void printEFGH(){cout << "Error" << m_i2 << endl;}
};

class C : virtual public A{
public:
    C(int i1):A(i1){}
    void printABCD(){cout << "C" << endl;}

};

class D : public C, public virtual A{
public:
    D(int i1):C(i1+1),A(i1){}
    void printABCD(){cout << "D" << endl;}
};

int _tmain(int argc, _TCHAR* argv[])
{
    A a(5); D d(7); C c(7); D* e = new D(7);
    d.printABCD();
    e->printABCD();
    ((A)d).printABCD();
    ((A*)e)->printABCD();
    ((B*)e)->printABCD();
    ((B*)e)->printEFGH();  // This line works perfectly, but why??
    //((B)(d)).printABCD(); //This will error and its right
    ((C*)e)->printABCD();
    ((C)d).printABCD();
    cout << a.GetMI1() << endl;
    cout << c.GetMI1() << endl;
    cout << d.GetMI1() << endl;
    system("pause");
    return 0;
}
Was it helpful?

Solution

Undefined behaviour is undefined. The moment you dereference (B*)e, your program has Undefined Behaviour (because e doesn't actually point to a B object), so anything can happen. The program could appear to work, it could crash, it could order pizza online.

One possible explanation for the behaviour could be that the layout of D happens to be such that the C subobject is first in memory and has the same size as int (it probably contains just a pointer to the virtual base A), and that is followed by the A subobject whose member m_i1 thus has the same offset from a D object start as the member m_i2 would have of a B object start. Therefore the code interprets that A::m_i1 as B::m_i2 when executing B::printEFGH(). But this is pure speculation. As with any UB, literally anything can happen.

OTHER TIPS

The compiler is generating code that does exactly what you're telling it to do (and you're "telling it" to invoke undefined behavior). The function your invoking is not virtual, and as such there the compiler can (and will) generate code that simply pushes this on to the stack and invokes a member function, literally as a call with no need for vtable resolution.

It is utterly undefined behavior and I hope that is clear, so don't do it. The value you're getting is whatever happens to be in memory offset from the base of the object you're passing (which is not a B derivation).

If you want to see something odd (and equally UB). add an int member m_dval to class D, then set it to 42 on D-construction, then do you UB invoke exactly as you have it. I think it may surprise you what is being reported as the m_i2 member value (which isn't, btw).

Firstly, your code contains invalid calls whose behavior is undefined.

Class D does not have class B among its ancestors. This means that any casts from D * to B * followed by any attempts to use the resultant pointer as pointing to an object of type B are already invalid. If you used C++-style cast (static_cast), the compiler would have told you about it (as it did in response to your (B)(d) attempt). There's no point in trying to analyze any calls performed after such casts. I.e. this

((B*)e)->printABCD();
((B*)e)->printEFGH();

has undefined behavior.

Secondly, you said that you were "experimenting with diamond problem in C++". The only part of your code that has some relation to "diamond problem" is actually this call

cout << d.GetMI1() << endl;

It shows you that D(7) -> A(7) initializer won over D(7) -> C(8) -> A(8) initializer. The rest of the calls are completely unrelated to any "diamond" effects.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top