这是VC ++ 2010中的错误吗?

时间:2013-11-23 11:38:33

标签: c++ visual-c++ derived-class

当我在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;
}

3 个答案:

答案 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)初始值设定项。其余的电话与任何“钻石”效果完全无关。