如果我们尝试以类似的方式“破解”对象数组,我试图看看在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];
,输出结果相同。
答案 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元素的结尾)。
如果你有多个虚拟继承并且编译器需要更改指向的地址,那么它只会对第一个元素进行。
所以:
无需复制任何内容,只有在需要时才会更改第一个元素的指针地址。
是的,只需在B中添加一些元素,使B大于A,就可以了解未定义行为的精彩世界。您也可以在B对象内复制C对象的A部分。
Nb:如果你想在同一个数组中管理一些B和C,你可以使用A **(或std :: whatever)然后它是安全的。 (但你可能已经知道了)。