Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/134.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++ 具有虚拟功能的类型转换_C++_Casting_Virtual - Fatal编程技术网

C++ 具有虚拟功能的类型转换

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

在下面的代码中,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 : 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之后