In C++, can we upcast an array and then try to put another subtype into it (inspired by Java ArrayStoreException)?

StackOverflow https://stackoverflow.com/questions/17384324

Pergunta

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.

Foi útil?

Solução

Actualy, when you rite A* bs = new B[5], you are doing something wrong. It will cause issue if the size of a B element isn't the same of a B element. Because you can upcast B* to A*, the conversion is safe ... if there was only ONE element.

Let's say A has a length of 32bits, and B a length of 64. Then, you'll have strange thing happening when you brows your bs array. (The bs[1] will be the end of the first B element).

If you have multiple virtual inheritance and the compiler need to change the pointed address, then it will only be done for the first element.

So :

  1. Nothing have to be copied, only the pointer address of the first element will be changed if needed.

  2. Yes, just add some elements to B so that B is bigger than A, and you are on the wonderful world on undefined behavior. You may also be able to copy the A part of a C object inside a B object doing so.

Nb : If you want to manage some B and C in the same array, you can use a A** (or a std::whatever) and then it will be safe. (But you probably already knows that).

Outras dicas

1) Yes, vptr is not copied in the assignment operator. Assignment operator doesn't change the runtime type of an object.

2) A* bs = new B[5]; is dangerous. What if B contains some data that A doesn't? Then sizeof(B) > sizeof(A), and accessing items through bs[i] causes undefined behaviour (segmentation fault on my system).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top