【问题标题】:Virtual tables when copying objects复制对象时的虚拟表
【发布时间】:2015-02-23 15:40:41
【问题描述】:
#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。为什么? 谢谢。

【问题讨论】:

  • 覆盖 A 的赋值运算符以查看发生了什么。
  • 正如在一些答案中所指出的,C++ 引用与基本上所有其他语言的引用完全不同。要在 C++ 中获得这种行为,您应该使用指针。
  • 问题:如何重新设置引用以使其引用不同的对象?答:没办法。 isocpp.org/wiki/faq/references#reseating-refs

标签: c++ virtual


【解决方案1】:

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

话虽如此,

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

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

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

如您所见,在整个很长的故事中,VTABLE 和 VPTR 根本不存在!

【讨论】:

  • +1 你能解释一下为什么虚拟指针是每个对象而不是每个类吗?谢谢。
【解决方案2】:

如果您使用指针(而不是引用),您的派生对象将保持应有的状态。那么 vtbl 就被正确调用了。

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

如果您使用引用,运行时系统不会理解您在 ref 中的内容是 B。当你做任务时,它认为它是A

【讨论】:

  • 我认为,指向 vtbl 的指针就像类的其他成员一样,并且像任何其他成员一样被复制。是不是真的?
  • @Sanich:不,vtable 不是成员。它反映并定义了对象的动态类型。
  • 按值返回本地创建的派生对象会丢弃 vtable... 这完全破坏了继承。使用指针,嗯.. 谁删除它,它在哪里创建?然后事情开始变得危险
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

    A& ref = a;
    ref = b;
    

    直接等价于:

    a = b;
    

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

    【讨论】:

    • 它甚至更直接等同于a.operator=(b)A::operator=(a,b),这(恕我直言)更好地强调了赋值是修改对象a 而不是更新引用ref
    【解决方案5】:

    我认为您对引用与指针感到困惑。让自己了解您的代码实际上做了什么

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

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

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

    • 原来的b,和
    • bA 部分复制到对象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

    【讨论】:

      【解决方案6】:

      A& ref = a; ref = b;

      您的引用的静态和动态类型是 A&amp; 。分配ref = b 不会按照您的期望更改引用的动态/静态类型。

      来自 Marshall Cline 的 C++ 常见问题解答

      你不能把引用和被引用分开。

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

      试试这个

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

      【讨论】:

      • 据我了解,每个虚拟类都有一个指向其虚拟表的指针。我的问题实际上是:当对象被复制时,该指针会发生什么?是否复制为普通会员?谢谢!
      猜你喜欢
      • 1970-01-01
      • 2022-01-08
      • 2019-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-09
      • 1970-01-01
      • 2015-07-04
      相关资源
      最近更新 更多