3

我创建了一个名为 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() 时如何让它工作?

4

6 回答 6

8

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

于 2009-05-03T06:41:12.410 回答
8

基类必须有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 的以下部分:

于 2009-05-03T06:55:27.787 回答
5

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

于 2009-05-03T06:38:10.497 回答
5

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

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

于 2009-05-03T06:42:57.850 回答
2

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

于 2009-05-03T07:27:00.567 回答
1

让我巩固以前的答案。

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

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

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

于 2009-05-03T09:27:55.923 回答