C++ 具有虚拟功能的类型转换
在下面的代码中,pC==pA:C++ 具有虚拟功能的类型转换,c++,casting,virtual,C++,Casting,Virtual,在下面的代码中,pC==pA: class A { }; class B : public A { public: int i; }; class C : public B { public: char c; }; int main() { C* pC = new C; A* pA = (A*)pC; return 0; } 但是当我在B中添加一个纯虚函数并在C中实现它时,pA!=个人电脑: class A { }; class B : pub
class A
{
};
class B : public A
{
public:
int i;
};
class C : public B
{
public:
char c;
};
int main()
{
C* pC = new C;
A* pA = (A*)pC;
return 0;
}
但是当我在B中添加一个纯虚函数并在C中实现它时,pA!=个人电脑:
class A
{
};
class B : public A
{
public:
int i;
virtual void Func() = 0;
};
class C : public B
{
public:
char c;
void Func() {}
};
int main()
{
C* pC = new C;
A* pA = (A*)pC;
return 0;
}
在这种情况下,为什么pA不等于pC?它们不是都指向内存中的同一个“C”对象吗?指向对象的指针可以转换为指向基本对象的指针,反之亦然,但转换不必很简单。基本指针的值与派生指针的值完全不同是完全可能的,而且通常是必要的。这就是为什么你有一个强大的类型系统和转换。如果所有的指针都是一样的,你就不需要任何一个了。以下是我基于这个问题的假设 1) 在这种情况下,您从C转换为a,获得了预期的行为。
2) 您添加了一个虚拟函数,该强制转换不再有效(因为在强制转换之后,您无法直接从中提取数据,您将获得对您毫无意义的数据) 如果这些假设是真的,那么您遇到的困难就是在B中插入虚拟表。这意味着类中的数据不再与基类中的数据完美地对齐(因为类中添加了对您隐藏的字节,即虚拟表)。一个有趣的测试是检查sizeof以观察未知字节的增长 要解决这个问题,您不应该直接从A转换到C来获取数据。您应该添加一个getter函数,该函数位于a中,由B和C继承
鉴于您在注释中的更新,我认为您应该阅读,它解释了虚拟表和内存布局,以及它是如何依赖于编译器的。该链接更详细地解释了我上面解释的内容,但给出了指针是不同值的示例。真的,我知道你为什么问错了问题,但似乎信息仍然是你想要的。从C到A的转换考虑了此时的虚拟表(注意C-8是4,在32位系统上,我相信这是虚拟表所需地址的大小) 您看到的指针值不同,因为新的虚拟函数导致将vtable指针注入到对象中。VC++将vtable指针放在对象的开头(这是典型的,但纯粹是内部细节) 让我们向添加一个新字段,以便更容易解释
class A {
public:
int a;
};
// other classes unchanged
现在,在内存中,您的pA
和A
如下所示:
pA --> | a | 0x0000004
在混合物中添加B和C后,您将得到以下结果:
pC --> | vtable | 0x0000000
pA --> | a | 0x0000004
| i | 0x0000008
| c | 0x000000C
如您所见,
pA
指向vtable后面的数据,因为它不知道vtable的任何信息或如何使用它,甚至不知道它在那里pC
确实知道vtable,所以它直接指向表,这简化了它的使用。你说它们相等是什么意思?这是否意味着您在案例A而不是案例B中从一个转换到另一个时获得了预期值?请尝试提供一个更接近您实际代码的示例,因为您的假设是错误的(错误在别处)。我可能有点困惑。我认为,如果pA和pC指向内存中的同一个对象,即使一个是A*,另一个是C*,它们的值也是相同的。我知道多重继承可以做到这一点,但我还没有弄清楚这个小谜题背后的机制。@TamásSzelei,您的测试无效,因为==
运算符将隐式转换其中一个指针。我修改它以显示相等的地址,但这也不能证明什么,因为不同的编译器可以自由地以不同的方式布局它们的对象。我想这就是我的困惑所在。我认为如果指针指向内存中的同一个对象,那么它们的值将是相同的。它们在没有虚函数的情况下具有相同的值。但是,当添加虚拟函数时,值是不同的。如果值不同,有没有办法检查pA和pC是否实际指向内存中的同一对象?@user987280:它们不指向同一对象。派生指针指向派生程度最高的完整对象,基指针指向基子对象。有时,基本子对象与包含最多派生对象的对象具有相同的地址,但这完全是巧合。“有时,基本子对象与包含最多派生对象的对象具有相同的地址,但这完全是巧合。”因此,请划掉虚函数,看看我发布的第一段代码。事实上,pA和pC具有相同的价值,这是不应该依赖的。我不知道,那是为了引起我的注意。在虚拟函数失败之前,我实际上是在依赖它。只是出于好奇,GCC的实现是如何工作的?地址是GCC的地址。@TamásSzelei,我不确定。大概GCC将B的vtable放在A的所有字段之后。或者,GCC可能会认识到,由于A没有自己的字段,所以在A和B(或C)之间进行转换时,不需要费心更改指针。从那时起,我问了A,得到了一个很好的答案。您的假设是正确的,它是空的基类优化。将字段添加到a
将使指针不同。@TamásSzelei,看起来GCC将B的vtable放在a的所有字段之后。所以在内存中有[a|u字段| b|vtbl | b|u字段| c|字段]。(在给出的示例中,A没有字段,但这在概念上不会改变布局。)我还没有确认GCC正是这么做的,但这似乎是最可能的情况。这意味着它正在优化强制转换(现在不需要指针偏移),而牺牲了虚拟函数调用(现在需要强制转换避免的偏移)。对不起,我重复了你的评论。我的第一个被弄坏了,我没意识到它通过了。挖了一个bi之后