【问题标题】:Virtual Table and Object Slicing虚拟表和对象切片
【发布时间】:2019-07-29 15:27:12
【问题描述】:

在对象切片中,当派生类对象被复制到基类对象时,派生类的_vptr是否也像Base类的其他成员一样被复制到基类的_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();
}

我观察到上述 sn-p 的结果如下。

Output
In Base

【问题讨论】:

  • vptr 是一个实现细节。语义不是用 vtable 和 vptr 来指定的。

标签: c++ vtable object-slicing vptr


【解决方案1】:

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

看看你的主要功能:

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

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

Base& operator=(const Base& other)

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

然后您可能会问,“为什么它不复制 vptr 呢?”原因是,如果它复制了 vptr,则 Base 类上的方法最终会使用 Derived 类上实现的方法。但是,总的来说,这些方法可以使用仅存在于 Derived 类中的数据成员(并且不存在于 Base 类中)。因此调用这些方法是没有意义的(数据在逻辑上不存在于 Base 类中),因此语言正确地选择不这样做。

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

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

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

【讨论】:

    【解决方案2】:

    所有已知的实现都使用 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++ 的工作方式。甚至很难想象与更改现有对象的类型相关的语义。

    【讨论】:

      猜你喜欢
      • 2011-03-29
      • 1970-01-01
      • 2018-11-26
      • 2018-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-28
      相关资源
      最近更新 更多