虚拟表和对象切片

时间:2019-03-07 16:47:37

标签: vtable object-slicing vptr

在对象切片中,将派生类对象复制到基类对象时,派生类的_vptr是否也像基类的其他成员一样被复制到基类的_vptr吗?如果不是,为什么?

class Base{  
public :
virtual void Display(){cout<<"In Base"<<endl;}
 };
class Derived:public Base{
public:
void Display(){cout<<"In Derived"<<endl;}

};
int main()
{
Derived objD;
Base objB;
objB = objD;
objB.Display();
}

对于上面的代码段,我观察到以下结果。

Output
In Base

2 个答案:

答案 0 :(得分:0)

所有已知的实现都使用vptr,但细节差异很大。 vptr是一种实现,用于表示多态类(具有虚拟功能的类)的对象的类型,有时还具有虚拟基类的类(非常依赖于实现)。

请注意,在许多情况下,涉及多个简单继承(单个非虚拟继承)的类具有多个vptr。

vptr值是所构造的最多派生对象的类型的函数。

对象的类型在用户的生存期内无法更改;在构造过程中,对象的类型会随着构造而改变;在破坏过程中,它会变回原样。 (在构造或破坏过程中使用名称或其他类型与对象不匹配的表达式来引用该对象是非法的。)

您可以重用现有对象的空间来构造不同类型的对象,但这不是同一对象:

struct X {
  A a;
  B b;
  // requires: construction doesn't throw
};

void replace (X &x) {
  x.~X(); 
  B &b = *new (&x) B; // hope no exception here
  b.f(); // use as a normal B object
  x.f(); // undefined: cannot use x as a real object
  X *p = &x; // legal: x isn't used as an object, only as an address
  b.~B(); // clean up ressources if needed
  new (&x) X; // valid: &x refers to storage, as if a void*
  // x refers to a valid X object 
}

这种对象重用不会改变任何现有对象的类型:replace(x)不仅作用于x(它通过破坏一个并重建一个而起作用),它作用于存储位置x中的。调用replace(x)之后,可以使用名称x来指代新构建的X对象,该对象已经有效地标识了先前存在的X对象。

在C ++中,甚至不允许对对象进行任何操作(使对象保持活动状态)。 vptr无法在已构造的对象上进行更改。

更改已声明对象的类型会破坏C ++类型系统和已声明对象的不变性。编译器不会知道要销毁哪个类,调用哪个虚函数,基类在哪里等。它将完全改变C ++的工作方式。甚至很难想象与更改现有对象的类型相关的语义。

答案 1 :(得分:0)

未复制vptr。让我们尝试推理一下为什么

查看主要功能:

Derived objD;
Base objB;
objB = objD;  // <-- HERE
objB.Display();

在第3行中,您将objD分配给objB。这实际上是在调用Base的赋值运算符(它是自动定义的):

Base& operator=(const Base& other)

,并且正在将objD作为Base&传递。因此,您的问题变成:“赋值运算符是否复制vptr”?答案是不”。默认的赋值运算符仅复制类上的字段。

然后您可能会问:“为什么它也不会复制vptr?”原因是,如果复制了vptr,Base类上的方法将最终使用Derived类上实现的方法。但是,从总体上讲,这些方法可以使用仅在Derived类上存在的数据成员,而在Base类上不存在的数据成员。因此,调用这些方法将是荒谬的(逻辑上,在基类上不存在该数据),因此该语言正确地选择了不这样做。

主要问题是,当您将派生类分配给基类时,您要分配给的变量仅包含基类的字段,因此派生类中的字段不在基类中类不会被复制。因此,在基类上调用Derived类中的方法没有任何意义。

请注意,如果要为派生类分配Base指针或Base引用,则不是这种情况。在这种情况下,原始的Derived类实例仍然存在。它位于内存中的某个位置,并且具有所有Base + Derived类字段。因此,在该实例上调用的Derived类的方法将可以访问这些字段,因此能够调用这些方法仍然有意义。

这就是为什么在C ++中(通过虚拟方法)进行多态性,您需要使用引用或指针的原因。参见此处进行类似的讨论: Why doesn't polymorphism work without pointers/references?