复制对象时的虚拟表

时间:2010-10-03 17:09:11

标签: c++ virtual

#include <iostream>
#include <string>
class A
{
public:
    A(int a) : _a(a) {}
    virtual ~A() {}
    virtual void f() const {std::cout << _a << std::endl;}
private:
    int _a;
};
class B : public A
{
public:
    B(int a, int b) : A(a), _b(b) {}
    virtual void f() const {std::cout << _b << std::endl;}
private:
    int _b;
};
int main()
{
    B b (1,2);
    A a (5);
    A& ref = a;
    ref = b;
    ref.f();
    return 0;
}

输出:

1

据我所知,在将派生(扩展)类对象复制到基类对象时,会剪切派生对象并仅复制基类数据。但我认为'ref'的虚拟表现在应该是'b'的虚拟表,所以'ref.f();'应该调用函数:

void B::f() const {std::cout << _b << std::endl;}

但是在复制之后'ref'的vtbl仍然是A类的vtbl。为什么? 感谢。

6 个答案:

答案 0 :(得分:3)

永远不会复制存储在每个多态对象中的虚拟表(或更准确地说,指向虚拟表的指针)。

你的“为什么”问题背后的原因并不十分清楚。 当然,它不会被复制。为什么要复制?虚拟表是一种描述具体对象的特定于类型的行为的方法。它基本上[间接]描述了对象的实际类型。由于在复制期间对象的实际类型不会更改,因此虚拟表保持不变。

答案 1 :(得分:3)

首先,'虚拟表'不是标准的C ++概念。它是实现动态绑定和实现虚函数的高度实现特定机制。

话虽如此,

  

但我认为是虚拟表   'ref'现在应该是虚拟的   表'b'所以'ref.f();'应该打电话   功能

这不正确。虚拟表是每个类而不是每个对象。每个对象只有Vptr。

'ref'的类型(如果你愿意,由typeid(ref).name确认)是'A&amp;'。当您指定'ref = b'时,将使用对象'b'作为参数调用'A'的隐式赋值运算符。该运算符只是盲目地将'b'的'A'子对象复制到'ref'引用的当前对象中(即'a')。因此,对象'a'现在具有与'b'的'A'子对象完全相同的状态。

正如您所看到的,在整个非常长的故事中,VTABLE和VPTR根本不存在!。

答案 2 :(得分:2)

如果使用指针(而不是refences),派生对象将保持原样。然后正确调用vtbl。

A* ref;
ref = &b;
ref->f();

Wen你使用引用,运行时系统不明白你在ref中的内容是B。当你完成任务时,它认为它是A

答案 3 :(得分:1)

不,虚拟表不作为指针,并且在分配给基类实例时不会被复制(这称为切片)。

首先,让我们澄清您的代码:

A& ref = a;
ref = b;

直接相当于:

a = b;

现在,这里发生的是A实例的内容被A实例内容的B部分替换。但是,结果仍然是A实例,因此,它指向A的虚拟表,正确调用A::f

答案 4 :(得分:0)

A& ref = a; ref = b;

您的参考的静态和动态类型为A&。赋值ref = b不会根据您的期望更改引用的动态/静态类型。

来自Marshall Cline的C ++ FAQ

  

您无法将引用与引用分开。

     

与指针不同,一旦引用绑定到对象,it can not be "reseated" to another object.在这个意义上,引用类似于const指针,例如int* const p

试试这个

A& ref = b;  //reference is bound to b [dynamic type of the referent is `B&`]) 
ref.f(); //Prints 2

答案 5 :(得分:0)

我认为你对Reference vs. Pointer感到困惑。让自己了解代码实际上做什么

A& ref = a;   // Reference
ref = b;

在第一行之后,refa是同一对象的两个名称​​ ref的有效类型为A

第二行将对象b分配给ref引用的对象(当然,a)。你现在有了

  • 原始b
  • A - b的一部分复制到对象a中(并由ref引用)。

复制后,复制对象的任何“B - ness”都会完全丢失,以及对象a的先前内容。 _a成员被复制,_b成员和B vtable被拼接掉了。

请改为尝试:

int main()
{
    B b (1,2);
    A a (5);
    A * ref = &a;  // pointer, not reference
    ref = &b;
    ref->f();
    return 0;
}

这可能会产生“2”,正如您可能预期的那样。它也不会覆盖对象a,就像第一个例子那样。