我发现的类似问题更多的是基于它的作用;我理解为派生类的基类指针的分配,例如Base* obj = new Derived()
是右侧被提升为Base *类型,但我想了解这种情况如何发生以及它如何允许的机制用于虚拟访问派生类方法。从在线搜索,有人将上述代码等同于Base* obj = new (Base*)Derived
,这导致了这种混乱。如果在编译时进行此类型转换,虚函数为什么以及如何访问正确的函数(Derived类的函数)?此外,如果这种转换是以我读取它的方式发生的,那么当我们将一个非继承类分配给Base * obj时,为什么会出错呢?谢谢,并为问题的简单性道歉。我想了解导致这种行为的原因。
注意:为清楚起见,在我的示例中,Derived公开继承自Base。
答案 0 :(得分:3)
从严格意义上讲,答案是"继承如何在运行时工作?"是"然而编译器 - 编写者设计它"。即,语言规范仅描述了要实现的行为,而不是实现它的机制。
从这个角度来看,以下应该被视为类比。编译器将执行类似于以下操作的内容:
鉴于课程Base
:
class Base
{
int a;
int b;
public:
Base()
: a(5),
b(3)
{ }
virtual void foo() {}
virtual void bar() {}
};
编译器将定义两个结构:一个我们将调用"存储布局" - 这定义了成员变量的相对位置以及该类对象的其他簿记信息;第二个结构是"虚拟调度表" (或vtable)。这是指向类的虚方法实现的指针结构。
现在让我们看一下派生类的等效结构Derived
:
class Derived : public Base
{
int c;
public:
Derived()
: Base(),
c(4)
{ }
virtual void bar() //Override
{
c = a*5 + b*3;
}
};
重要的观察结果是成员a
和b
以及方法foo
和{{1}的成员变量存储和vtable条目的内存中表示基类和子类之间是相同的。因此,恰好指向类型为bar
的对象的类型为Base *
的指针仍将实现对变量Derived
的访问,作为对vtable指针之后的第一个存储偏移的引用。同样,调用a
将控制传递给vtable的第二个插槽中的方法。如果对象的类型为ptr->bar()
,则为Base
;如果对象的类型为Base::bar()
,则为Derived
。
在这个类比中,Derived::bar()
指针指向成员存储块。因此,this
的实现可以通过相对于Derived::bar()
获取vtable指针之后的第三个存储槽来访问成员变量c
。请注意,只要this
位于第二个vtable插槽中,即当对象确实属于Derived::bar()
类型时,就会存在此存储槽。
对于在Derived
偏移0处使用文字vtable指针的编译器破坏vtable指针可能产生的调试精神错误:
this
收率:
#include <iostream>
class A
{
public:
virtual void foo()
{
std::cout << "A::foo()" << std::endl;
}
};
class B
{
public:
virtual void bar()
{
std::cout << "B::bar()" << std::endl;
}
};
int main(int argc, char *argv[])
{
A *a = new A();
B *b = new B();
std::cout << "A: ";
a->foo();
std::cout << "B: ";
b->bar();
//Frankenobject
*((void **)a) = *((void **)b); //Overwrite a's vtable ptr with b's.
std::cout << "Franken-AB: ";
a->foo();
}
...请注意$ ./a.out
A: A::foo()
B: B::bar()
Franken-AB: B::bar()
$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609
和A
之间缺少继承关系......:scream:
答案 1 :(得分:0)
无论谁说
Base* obj = new Derived();
相当于
Base* obj = new (Base*)Derived;
对主题一无所知。
更像是:
Derived* temp = new Derived;
Base* obj = temp;
不需要显式演员表。该语言允许将派生类指针分配给基类指针。
大多数情况下,两个指针的数值相同但在涉及多个继承或虚拟继承时它们不相同。
在将派生类指针转换为基类指针时,编译器有责任确保指针的数值正确偏移。编译器能够这样做,因为它决定派生类的布局和派生类对象中的基类子对象。
如果在编译时进行此类型转换,虚拟函数为何以及如何访问正确的函数
没有类型转换。有一种类型转换。关于虚拟功能,请参阅How are virtual functions and vtable implemented?。
此外,如果按照我读取的方式进行此转换,为什么在将非继承类分配给
Base*
obj时会出错?
这是没有实际意义,因为它不会像你想象的那样发生。