Я попытался посмотреть, что произойдет на C++, если мы попытаемся «разбить» массив объектов аналогичным образом, мы можем попытаться сделать это на Java.В C++, можем ли мы оптимизировать массив, а затем попытаемся включить в него еще один подтип (вдохновленный Java ArrayStoreException)?
В Java мы можем иметь массив типа Double [], например, увеличить его до Number [] (поскольку Double является подклассом числа) и попытаться добавить другой подкласс Number to array, например, Integer. Код будет скомпилирован, но мы получим 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
Смотрите, как создание А или С, а затем скопировать его в массив 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];
, и результат был таким же.
C++-аналог Java 'Double [] ds' -' Double * ds [] ', то есть массив указателей **, а не ** массив объектов. –