在C++;,我们是否可以向上转换一个数组,然后尝试将另一个子类型放入其中(受Java ArrayStoreException启发)? 我尝试着看看C++中会发生什么,如果我们试图用类似的方式“中断”一组对象,我们可以尝试用java来做。

在C++;,我们是否可以向上转换一个数组,然后尝试将另一个子类型放入其中(受Java ArrayStoreException启发)? 我尝试着看看C++中会发生什么,如果我们试图用类似的方式“中断”一组对象,我们可以尝试用java来做。,java,c++,arrays,vtable,vptr,Java,C++,Arrays,Vtable,Vptr,在Java中,我们可以有一个Double[]类型的数组,例如,将它向上转换为Number[](因为Double是Number的子类),然后尝试将Number的另一个子类添加到数组中,例如整数。代码可以编译,但我们会在运行时得到ArrayStoreException,因为Integer类型将在运行时根据数组的实际类型进行检查,而数组的实际类型恰好是双精度的,当然会存在不匹配。代码可能如下所示: Double[] ds = new Double[12]; Number[] ns = ds; ns[0

在Java中,我们可以有一个Double[]类型的数组,例如,将它向上转换为Number[](因为Double是Number的子类),然后尝试将Number的另一个子类添加到数组中,例如整数。代码可以编译,但我们会在运行时得到ArrayStoreException,因为Integer类型将在运行时根据数组的实际类型进行检查,而数组的实际类型恰好是双精度的,当然会存在不匹配。代码可能如下所示:

Double[] ds = new Double[12];
Number[] ns = ds;
ns[0] = 2.3;              // OK
ns[1] = new Integer(1);   // compiles, but we have ArrayStoreException in runtime
所以我想,C++怎么样?我们能试着玩同样的把戏吗?运行时会发生什么

这是我试过的代码和输出

#include <iostream>

class A
{
public:
    A(int v = 0): val(v) {}
    virtual void g() {std::cout << val << " in A\n";}
    void setVal(int v) {val = v;}
protected:
    int val;
};

class B : public A
{
public:
    virtual void g() {std::cout << val << " in B\n";}
};

class C : public A
{
public:
    C(int v = 0): A(v) {}
    virtual void g() {std::cout << val << " in C\n";}
private:
    int stuff[10];
};


void f(A* as)
{
    as[1] = *(new A(12));
}

void f2(A* as)
{
    as[1] = *(new C(22));
}

int main()
{
    A* bs = new B[5];
    for (int i=0 ; i<5; ++i)
    {
        bs[i].setVal(i);
        bs[i].g();
    }

    std::cout << std::endl;

    f(bs);
    for (int i=0 ; i<5; ++i)
    {
        bs[i].g();
    }

    std::cout << std::endl;

    f2(bs);
    for (int i=0 ; i<5; ++i)
    {
        bs[i].g();
    }
}
请注意,创建A或C并将其复制到B数组中都会复制数据(并且,正如预期的那样,对于C,仅复制A的一部分数据-复制元素后没有内存损坏),但选择了B的方法,这意味着必须没有复制vptr

因此,我的问题是:

  • 我猜vptr不是在默认赋值运算符中复制的。是这样吗?这是我们调用B中的方法而调用C对象中的数据的唯一可能原因吗

  • 我们真的能想出一个例子,让一些坏的或意想不到的事情发生,一些运行时失败吗?我想,我所说的坏的意思是,有一个B数组,但其中有一个A或C的对象(不是B或B的子类型),一个与B“异类”的对象

  • <> >或者C++语言确实通过其特征的某些组合明确地或隐含地保证这是不可能发生的(例如java当它显式地提高ARRAYStureExtExchange)?

UPD:

A* bs = new B[5];
实际上,我在最后一刻更改了这一行,以强调B方法的运行时选择(不应该这样做,这是显而易见的,因为该方法是虚拟的)。我最初有
B*bs=newb[5]和输出是相同的。

1)是的,
vptr
未在赋值运算符中复制。赋值运算符不会更改对象的运行时类型


2)
A*bs=新B[5]是危险的。如果
B
包含一些
A
不包含的数据,该怎么办?然后
sizeof(B)>sizeof(A)
,通过
bs[i]
访问项目会导致未定义的行为(在我的系统上出现分段错误)。

实际上,当您编写A*bs=new B[5]时,您正在做一些错误的事情。如果B元素的大小与B元素的大小不同,则会导致问题。因为可以将B*向上转换为A*,所以转换是安全的。。。如果只有一个元素

假设A的长度为32位,B的长度为64位。然后,当您浏览bs数组时,会发生奇怪的事情。(bs[1]将是第一个B元素的末尾)

如果您有多个虚拟继承,并且编译器需要更改指向的地址,那么它将只对第一个元素执行

因此:

  • 无需复制任何内容,只要在需要时更改第一个元素的指针地址

  • 是的,只要在B中添加一些元素,使B比A大,你就可以在未定义行为的奇妙世界上。您还可以在B对象中复制C对象的A部分,这样做


  • 注意:如果你想在同一个数组中管理一些B和C,你可以使用a**(或者std::随便什么),这样就安全了。(但是你可能已经知道).java的C++模拟类似于Java的代码> double []ds<代码>是代码>双*DS []/COD>,即指针数组,而不是对象数组。2)我在最后一刻实际上改变了这行,强调B方法的运行时选择(不应该做,很明显,因为方法是虚拟的). 我最初有
    B*bs=newb[5]和输出是相同的。
    
    A* bs = new B[5];