【问题标题】:Why won't a derived class work in an array? (C++)为什么派生类不能在数组中工作? (C++)
【发布时间】:2010-10-23 10:06:09
【问题描述】:

我创建了一个名为 vir 的类,带有一个函数 move:

class vir
{
public:
     vir(int a,int b,char s){x=a;y=b;sym=s;}
     void move(){}
};

(它派生自具有变量 int x、int y 和 char sym 的类) 我从中派生了一个类,称为 subvir:

class subvir:public vir
{
public:
     subvir(int a,int b,char s){x=a;y=b;sym=s;}
     void move();
};
subvir::move()
{
     x++;
     return;
}

然后我创建了一个 vir 数组,并将一个 subvir 放入其中

subvir sv1(0,0,'Q');
vir vir_RA[1]={sv1};

但是当我尝试使用 sv1.move() 时:

vir_RA[0].move();

它使用 vir move ({}) 而不是 subvir move ({x++})。我尝试将 sv1 设为 vir,将 vir_RA 设为 vir,它可以工作,当我将它们都设为 subvir 时它也可以工作,但我需要它们不同。我尝试使 vir::move() 成为纯虚拟,但随后我得到一个错误来证实数组。有谁知道当我从数组中使用 move() 时如何让它工作?

【问题讨论】:

标签: c++ arrays class inheritance polymorphism


【解决方案1】:

您遇到了一个名为slicing 的问题。使用指针数组,或类似Boost.ptr_container

【讨论】:

  • 这里没有进行切片。 (反正还没有。) move() 不是虚函数,所以 vir* 会调用 vir::move()
  • @jmucchiello:这里肯定会发生切片——vir_RA 的第一个元素将使用编译器为类型 vir 自动生成的复制构造函数从 sv1 复制初始化(实际上,复制构造)。跨度>
【解决方案2】:

基类必须有virtual 函数才能得到你想要的东西,使这些纯粹会产生一个抽象基类——你无法实例化的东西。但是,您仍然可以创建指向抽象基类的指针/引用并将派生类对象分配给它们。您的基类最好表示为:

class vir
{
public:
     vir(int a,int b,char s){x=a;y=b;sym=s;}
     virtual void move(){}
};

这使得派生类的move 也是虚拟的。但是,您的 move 定义缺少返回值并且不会编译。试试:

void subvir::move()
{
     x++;
     return;
}

请注意,您需要指针(如其他答案中所述)或对派生类的引用才能使动态绑定起作用。因此,不要使用 vir 对象数组,而是使用基类指针数组:

vir* v[ 2 ] = { new subvir(0, 0, 'Q'), new subvir(10, -10, 'P') };

你也应该 请阅读 C++ FAQ Lite 的以下部分:

【讨论】:

  • 最后一个 sn-p 是错误的来源:你将两个临时对象的地址存储在数组中。在指令(分号)结束时,两个临时对象都将被销毁,并且数组将保持指向无处的指针。
  • 是的。我真正想到的是new subvir...——更新了我的答案。
【解决方案3】:

在这种情况下,您需要一个指针数组,而不是实例数组。使用 vir*[] 代替 vir[]

【讨论】:

  • 谢谢,这对我帮助很大。
【解决方案4】:

两件事。该数组是 vir 的数组,所以它当然使用 vir::move。 move() 不是虚方法。

但更重要的是切片。您不能将子类放入数组中。如果 sizeof vir != sizeof subvir,数组将无法正确排列。目前它们的大小相同。但如果他们不是,会发生什么。

【讨论】:

    【解决方案5】:

    是的,基本上编译器不允许数组中的子类,因为 数组针对类型大小进行了严格初始化,并且子类型倾向于 比父母大,如果可以的话,会导致问题 用子类型值初始化数组。 真正发生的是编译器首先分配数组 N * size(base_type) 字节。 然后它复制每个初始化的 size(base_type) 字节 对象。如果它们是不同的类型,它们会被截断, 你的代码中可能会发生奇怪的事情。

    【讨论】:

    • +1。或多或少。当然,“复制大小(base_type)字节”只是默认行为——如果 base_type 提供了用户定义的复制构造函数,则该复制构造函数将被调用,其“base_type const&”参数指的是派生类型对象被用作初始化程序。
    【解决方案6】:

    让我把之前的答案整理一下。

    这里实际上有两个问题。一是切片。您正在使用 subvir 的副本初始化 virs 数组。在这种情况下,编译器将 vir 部分从 subvir 中切出并将其复制到数组中,因此您确实只能在那里获得 vir 对象。现在在您的特定情况下,subvir 除了 vir 之外没有其他数据成员,因此切片有点退化,vir 对象看起来很像 subvir 对象。但是,vir 和 subvir 是不同的类,数组中的对象最终是 vir 对象,而不是伪装成 vir 的 subvir 对象。即使两者具有相同的数据成员,两者之间的差异实际上也会体现出来的一种方式是,如果 vir 具有被 subvir 重载的虚函数。在这种情况下,数组中对象中的 vtable 指针将指向 vir 的 vtable,而不是 subvir 的。当然,如果 subvir 包含 vir 中没有的额外数据成员,那就更明确了。

    第二个问题是多态性。在使用点(对 move() 的调用),编译器认为您正在调用 vir 类型的对象的 move() 方法(因为该数组是一个 virs 数组)。 (编译器的想法当然是正确的,由于切片,在这种情况下可能是退化的。)如果它实际上是你想要的 subvir 对象,你可以通过 make move() 调用 subvir::move() ) 在 vi​​r 中虚拟。

    要获得所需的行为,您可以使用指针数组(但是您将直接在 sv1 上操作,而不是它的副本,除非您首先创建一个副本并使用指向该副本的指针初始化该数组)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-27
      • 2016-02-14
      • 1970-01-01
      • 1970-01-01
      • 2022-12-11
      • 1970-01-01
      • 2011-06-17
      相关资源
      最近更新 更多