Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/126.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
调试C++;VisualStudio2008监视窗口中的虚拟多重继承 我在VisualStudioC++ 2008中调试一个项目,在指针上有指向多个虚拟继承对象的麻烦。如果指针是基类型,我无法检查派生类中的字段_C++_Visual Studio 2008_Debugging - Fatal编程技术网

调试C++;VisualStudio2008监视窗口中的虚拟多重继承 我在VisualStudioC++ 2008中调试一个项目,在指针上有指向多个虚拟继承对象的麻烦。如果指针是基类型,我无法检查派生类中的字段

调试C++;VisualStudio2008监视窗口中的虚拟多重继承 我在VisualStudioC++ 2008中调试一个项目,在指针上有指向多个虚拟继承对象的麻烦。如果指针是基类型,我无法检查派生类中的字段,c++,visual-studio-2008,debugging,C++,Visual Studio 2008,Debugging,我制作了一个简单的测试用例: class A { public: A() { a = 3; }; virtual ~A() {} int a; }; class B : virtual public A { public: B() { b = 6; } int b; }; class C : virtual public A { public: C() { c = 9; }

我制作了一个简单的测试用例:

class A
{
    public:
        A() { a = 3; };
        virtual ~A() {}
        int a;
};

class B : virtual public A
{
    public:
        B() { b = 6; }
        int b;
};

class C : virtual public A
{
    public:
        C() { c = 9; }
        int c;      
};

class D : virtual public B, virtual public C
{
    public:
        D() { d = 12; }
        int d;
};

int main(int argc, char **argv)
{
    D *pD = new D();
    B *pB = dynamic_cast<B*>(pD);

    return(0);
}
A类
{
公众:
A(){A=3;};
虚拟~A(){}
INTA;
};
B类:虚拟公共A
{
公众:
B(){B=6;}
int b;
};
C类:虚拟公共A
{
公众:
C(){C=9;}
INTC;
};
D类:虚拟公共B、虚拟公共C
{
公众:
D(){D=12;}
int d;
};
int main(int argc,字符**argv)
{
D*pD=新的D();
B*pB=动态铸造(pD);
返回(0);
}
在“return(0)”上放置一个断点,并在watch窗口中放置pD和pB。我想不出办法在手表窗口的pB中看到“d”。调试器将不接受C样式转换或动态转换。扩展到v表显示调试器知道它实际上指向一个D析构函数,但无法看到“D”

从基类定义中删除“虚拟的”(因此D有2个A),调试器将允许我展开pB,并看到它实际上是一个可以展开的D*对象。这也是我想在虚拟案例中看到的


有什么办法可以让这一切顺利进行吗?我需要计算出对象布局的实际偏移量才能找到它吗?还是说我不够聪明,不适合虚拟多重继承和重新设计,因为实际的项目要复杂得多,如果我不能调试,我应该让它更简单:)

仔细看看pB和pD的实际指针值。要使指针调整正确是很困难的,需要一个编译器。

在我看来,需要多重和虚拟继承的时间非常少,甚至可能有更好的方法对域进行建模。继承本身在基类和派生类之间创建了紧密耦合,因此在菱形树中添加会创建一组紧密耦合的类,这些类最终将以脊形设计结束

除此之外。我在vs2003和vs2005中编译了您的代码,它们都在watch窗口中显示了以下内容

pD + B { b=6 } + C { c=9 } d 12 pD +B{B=6} +C{C=9} d 12
好吧,我终于有点事要做了玩指针运算,所以我来回答我自己的问题。宣布一项全球战略:

D d;
现在我可以把它放到调试器中,我可以看到包含pB指向的B的D对象的内容:

(D*)((char *) pB + (((char *)&d.d) - ((char *)&d.b)))
所以基本上,我只需要定义一个只调试的D实例,我可以用它来查找指针偏移量

奇怪的是,调试器似乎在使用运行时类型标识来计算&d.d和&d.b的地址偏移量。如果我尝试一个不指向d实例的内存地址,调试器会给出错误的答案!这:

&((D *)(void *) pB)->b
&((D *)(void *) pB)->d
实际显示两个值的相同地址!真奇怪

这个解决方案并不漂亮,但很有效。我可能只能创建调试全局变量来使用。看起来调试器应该能够自动获取此信息,但实际上并没有。哦,好吧

这还表明调试符号引擎在虚拟基类的多重继承方面存在问题

但是如果您只是想要帮助调试,为什么不在类a上添加一个helper函数来获取D指针(如果可用的话)。您可以观看pB->GetMyD()

D类;
甲级
{
...
D*GetMyD();
...
}
D类。。。
D*A::GetMyD()
{
返回动态_cast(this);
}

这将把指针算法留给编译器。

据我所知,实际上没有安全的恢复方法。
如果您查看pB和pD的内存地址,您会注意到它们并不相同

D *pD = new D(); // points at 0x00999720

B *pB = dynamic_cast<B*>(pD); // points at 0x00999730, 
// hence inside the memory segment of pD
在“监视”窗口中工作,但将显示错误的值,因为指出的内存实际上从0x00999730开始,而不是原来的0x00999720

在您的示例中,重新解释_cast将导致:

D* pD2 = reinterpret_cast<D*>(pB); // or "(D*)(void*)pD" in the watch window
pD2
 + B {b=6}
   + A {a=3}
 + C {c=6}
   + A {a=3}    
 d=6
因此,正是最初的动态演员阵容把事情搞得一团糟

编辑(需要注意的其他内容):
把事情搞砸的是,你假设pB实际上仍然是D,事实并非如此。由于虚拟继承,pB实际上仅在从D**转换时指向a B
这是由于类是如何在内部表示的。
正常继承可以被认为是产生如下内存结构:

struct A
{
    int a;
}
struct B
{
    A base
    int b;
}
struct A
{
    int a;
}
struct B
{
    A* base
    int b;
}
struct D
{
    B* base1;
    C* base2;
    int d;
}
虽然虚拟继承会产生如下结果:

struct A
{
    int a;
}
struct B
{
    A base
    int b;
}
struct A
{
    int a;
}
struct B
{
    A* base
    int b;
}
struct D
{
    B* base1;
    C* base2;
    int d;
}
这是因为虚拟继承是为了防止重复,它使用指针来防止重复。如果您有:

class A
class B: virtual public A
class C: virtual public A
class D: virtual public B, virtual public C
D可以这样想:

struct A
{
    int a;
}
struct B
{
    A base
    int b;
}
struct A
{
    int a;
}
struct B
{
    A* base
    int b;
}
struct D
{
    B* base1;
    C* base2;
    int d;
}
其中,B和C的A*基点指向A的同一实例。 因此,当您将D强制转换为B时,pB将指向D的base2,而不是具有与正常单继承相同的内存起点

无虚拟多重继承的Samething

class A
class B
class C: public A, public B
将产生一个可以被认为是:

struct C
{
    A base1;
    B base2;
    int c;
}
因此,如果您这样做:

{
    C *pC = new C();
    B *pB = dynamic_cast<B*>(pC);
    C *pC2 = reinterpret_cast<C*>(pB);
}
{
C*pC=新的C();
B*pB=动态_铸造(pC);
C*pC2=重新解释铸件(pB);
}
它将失败,因为pB实际上指向base2,而base2与pC的内存地址不同,后者与base1相同

免责声明
上述表述可能并不完全正确。这是一个简化的心智模型,大多数时候对我来说都是有效的。可能存在此模型不正确的情况

结论: 多重继承和任何类型的虚拟继承都可以防止以安全的方式重新解释回溯到子类型。
MS VC++(Visual Studio中使用的C++编译器)实现非虚拟多重继承的方式,您可以从超类列表中的第一个基类型回溯到子类。不知道这是根据C++规范还是其他编译器怎么做的。