当我在C ++中尝试钻石问题时,我遇到了这个问题。 以下程序适用于Visual Studio 2010,我得到以下内容。
D
D
A
A
B
Error7
C
C
5
7
7
Press any key to continue . . .
如果您有任何解释,请向我解释。
请注意,任何B对象都不会以任何方式构造。大多数派生类D仅来自C和A.不是B.我想知道这个m_i2从哪里获得价值7?
#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;
}
答案 0 :(得分:3)
Undefined behaviour is undefined.
当你取消引用(B*)e
时,你的程序有未定义的行为(因为e
实际上没有指向B
对象),所以任何事情都可能发生。该程序看似可行,可能会崩溃,可以在线订购披萨。
行为的一个可能的解释可能是D
的布局恰好是C
子对象在内存中的第一个并且与int
具有相同的大小(可能是仅包含指向虚拟基础A
的指针),然后是A
子对象,其成员m_i1
因此与D
对象的偏移量相同成员m_i2
将有B
个对象开始。因此,代码在执行A::m_i1
时将B::m_i2
解释为B::printEFGH()
。但这是纯粹的猜测。与任何UB一样,任何事情都可能发生。
答案 1 :(得分:2)
编译器生成的代码完全你要告诉它做什么(并且你“告诉它”调用未定义的行为)。您调用的函数不是虚拟的,因此编译器可以(并且将)生成只是将this
推送到堆栈并调用成员函数的代码,字面上只需call
而不需要vtable
解决方案。
完全未定义的行为,我希望这是明确的,所以不要这样做。你得到的值是在内存中偏离你传递的对象底部的任何值(这是不是 B的推导)。
如果你想看到奇怪的东西(同样是UB)。将int
成员m_dval
添加到class D
,然后在D-construction上将其设置为42,然后UB就会调用完全。我认为可能会让您感到惊讶的是m_i2
会员价值(不是,顺便说一下)。
答案 2 :(得分:1)
首先,您的代码包含行为未定义的无效调用。
类D
在其祖先中没有类B
。这意味着从D *
到B *
的任何强制转换后,任何尝试使用结果指针指向类型为B
的对象都已无效。如果您使用C ++ - 样式转换(static_cast
),编译器会告诉您它(就像它响应您的(B)(d)
尝试一样)。尝试分析此类演员之后执行的任何调用是没有意义的。即此
((B*)e)->printABCD();
((B*)e)->printEFGH();
有未定义的行为。
其次,你说你是“在C ++中试验钻石问题”。代码中与“钻石问题”有一定关系的唯一部分实际上就是这个调用
cout << d.GetMI1() << endl;
它向您显示D(7) -> A(7)
初始值设定项胜过D(7) -> C(8) -> A(8)
初始值设定项。其余的电话与任何“钻石”效果完全无关。