在C ++中,我们可以向上转换数组,然后尝试将另一个子类型放入其中(受Java ArrayStoreException启发)吗?

时间:2013-06-29 20:19:11

标签: java c++ arrays vtable vptr

如果我们尝试以类似的方式“破解”对象数组,我试图看看在C ++中会发生什么。我们可以尝试用Java来实现它。

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

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();
    }
}

输出:

0 in B
1 in B
2 in B
3 in B
4 in B

0 in B
12 in B
2 in B
3 in B
4 in B

0 in B
22 in B
2 in B
3 in B
4 in B

看到创建A或C然后将其复制到B的数组中都会复制数据(并且,正如预期的那样,在C的情况下,只复制属于A的数据 - 复制的元素之后没有内存损坏),但选择了B的方法,这意味着必须没有复制vptr。

所以我的问题是:

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

  • 我们能否真正想出一个可以让一些不良或意外事情发生的例子,一些运行时失败?我的意思是,我的意思是,有一个 Bs 数组,但是其中包含A或C的对象(不是B的B或的子类型),对B来说是“外星人”的对象?

  • 或者C ++语言可以通过其功能的某些组合明确地或隐含地保证这不会发生(比如Java显式引发ArrayStoreException时)?

UPD:

A* bs = new B[5];

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

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

Actualy,当你用A * bs = new B [5]时,你做错了什么。如果B元素的大小与B元素的大小不同,则会引起问题。因为您可以将B *向上转换为A *,所以转换是安全的......如果只有一个元素。

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

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

所以:

  1. 无需复制任何内容,只有在需要时才会更改第一个元素的指针地址。

  2. 是的,只需在B中添加一些元素,使B大于A,就可以了解未定义行为的精彩世界。您也可以在B对象内复制C对象的A部分。

  3. Nb:如果你想在同一个数组中管理一些B和C,你可以使用A **(或std :: whatever)然后它是安全的。 (但你可能已经知道了)。