为什么派生类不能在数组中工作?(C++)

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() 正常工作吗?


请注意,初始化器是初始化成员字段和基对象的首选方法。详细信息请参阅http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6。示例:subvir(int a,int b,char s) : vir(a,b,s) {} - outis
6个回答

8
基类必须有虚函数才能得到你想要的结果,将这些函数设置为纯虚函数会导致抽象基类——这是无法实例化的。但是,您仍然可以创建指向抽象基类的指针/引用,并将派生类对象分配给它们。最好将您的基类表示为:
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的以下部分:


1
最后一段代码是错误的源头:你正在将两个临时对象的地址存储在数组中。在指令结束时(分号),这两个临时对象都将被销毁,而数组将持有无效的指针。 - David Rodríguez - dribeas
返回翻译后的文本:真正我想要的是 new subvir... -- 我更新了我的回答。 - dirkgently

8

这里还没有进行切片。 (目前还没有)move()不是虚函数,因此vir *将调用vir :: move()。 - jmucchiello
2
@jmucchiello:这里肯定会发生切片——vir_RA的第一个元素将使用编译器自动生成的vir类型的复制构造函数,从sv1中进行复制初始化(实际上是复制构造)。 - j_random_hacker

5

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


5

有两个问题。数组是vir的数组,因此当然使用vir::move。move()不是虚方法。

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


2

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


1
多或少。当然,“复制大小(base_type)字节”只是默认行为——如果base_type提供了用户定义的复制构造函数,那么该复制构造函数将被调用,并且其“base_type const&”参数将引用作为初始化器使用的derived_type对象。 - j_random_hacker

1

让我总结一下之前的答案。

实际上有两个问题。一个是切片问题。你正在用子vir的副本初始化一个vir数组。在这种情况下,编译器会将vir部分从subvir中切片出来,并将其复制到数组中,所以你确实只得到了vir对象。现在在你的特定情况下,subvir除了vir的数据成员外没有其他额外的数据成员,所以切片是有些退化的,vir对象看起来很像subvir对象。然而,vir和subvir是不同的类,数组中的对象最终是一个vir对象,而不是伪装成vir的subvir对象。如果vir被subvir重载了虚函数,那么两者之间的区别在实际中会表现出来。在这种情况下,数组中对象的vtable指针将指向vir的vtable,而不是subvir的vtable。当然,如果subvir包含了vir中没有的额外数据成员,差异就更加明显了。

第二个问题是多态性。在使用点(调用move())时,编译器认为您正在调用类型为vir的对象的move()方法(因为数组是vir的数组)。 (当然,由于切片,即使在这种情况下它是退化的,编译器认为是正确的。)如果它实际上是一个subvir对象,正如您所打算的那样,您可以通过将move()虚拟化在vir中来调用subvir::move()。
要获得所需的行为,您可以使用指针数组(但是除非您首先创建副本并使用指向副本的指针初始化数组,否则您将直接操作sv1,而不是其副本)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接