我试图看看如果我们尝试以类似的方式“破坏”一个对象数组,在 C++ 中会发生什么,我们可以尝试在 Java 中这样做。
在 Java 中,我们可以有一个 Double[] 类型的数组,例如,将其向上转换为 Number[](因为 Double 是 Number 的子类),并尝试将 Number 的另一个子类添加到数组中,例如 Integer。代码会编译,但我们会在运行时得到 ArrayStoreException,因为在运行时会根据数组的实际类型(恰好是 Double)检查 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();
}
}
输出:
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 时)?
升级版:
A* bs = new B[5];
我实际上是在最后一刻更改了这一行,以强调B方法的运行时选择(不应该这样做,因为方法是虚拟的,所以很明显)。我最初有B* bs = new B[5];
并且输出是相同的。