I have tried to see what would happen in C++ if we try to "break" an array of objects in a similar way we can try to do it in Java.
In Java we can have an array of type Double[], for example, upcast it to Number[] (because Double is a subclass of Number), and try to add another subclass of Number to array, for example, an Integer. The code would compile, but we would get ArrayStoreException in runtime, because the type Integer would be checked against the actual type of array, which happens to be Double, in runtime, and there would of course be a mismatch. The code could look like this:
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
So I thought - what about C++? Can we try to perform the same trick? What will happen in runtime?
Here's the code I tried, and the output.
#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();
}
}
Output:
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
See that both creating A or C and then copying it inside the array of B does copy data (and, as expected, in case of C only the data that is part of A is copied - no memory corruption after the copied element), but the method of B is selected, meaning that vptr must have not been copied.
So my questions are:
I guess that vptr is not copied in default assignment operator. Is it so? Is it the only possible reason that we have method from B called but data from C object?
Can we actually come up with an example that can make some bad or unexpected things happen, some runtime failure? What I mean by bad, I guess, is to have an array of Bs, but an object of A or C in it (not B or subtype of B), an object that is "alien" to B?
Or maybe the C++ language does guarantee explicitly or implicitly by some combinations of its features that this cannot happen (like Java when it explicitly raises ArrayStoreException)?
UPD:
A* bs = new B[5];
I actually changed this line at the last moment, to put emphasis on runtime selection of B method (should not have done it, it is obvious because the method is virtual). I initially had B* bs = new B[5];
and the output was the same.