Я попытался увидеть, что произойдет в C ++, если мы попытаемся «разбить» массив объектов аналогичным образом, как мы можем попытаться сделать это в Java.
В Java мы можем иметь массив типа Double [], например, преобразовать его в Number [] (потому что Double является подклассом Number) и попытаться добавить другой подкласс Number в массив, например, 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
Обратите внимание, что при создании A или C и последующем копировании их в массив B происходит копирование данных (и, как и ожидалось, в случае C копируются только данные, являющиеся частью A — без повреждения памяти после скопированного элемента), но выбран метод B, это означает, что vptr не должен быть скопирован.
Итак, мои вопросы:
Я думаю, что vptr не копируется в операторе присваивания по умолчанию. Это так? Это единственная возможная причина, по которой мы вызываем метод из B, но данные из объекта C?
Можем ли мы на самом деле придумать пример, который может привести к некоторым плохим или неожиданным событиям, некоторым сбоям во время выполнения? Что я имею в виду под плохим, я думаю, это иметь массив Bs, но объект А или С в нем (не B или подтип B) объект, который является «чужим» для B?
Или, может быть, язык C ++ явно или неявно гарантирует некоторыми комбинациями своих функций, что это не может произойти (например, Java, когда он явно вызывает ArrayStoreException)?
UPD:
A* bs = new B[5];
Я на самом деле изменил эту строку в последний момент, чтобы сделать упор на выбор метода времени выполнения метода B (это не следовало делать, это очевидно, потому что метод является виртуальным). У меня изначально было B* bs = new B[5];
и вывод был таким же.
На самом деле, когда вы читаете A * bs = new B [5], вы делаете что-то не так. Это вызовет проблему, если размер элемента B не совпадает с размером элемента B. Поскольку вы можете повысить значение B * до A *, преобразование безопасно … если был только ОДИН элемент.
Допустим, A имеет длину 32 бита, а B — 64. Тогда, когда вы просматриваете массив bs, у вас будет странная вещь. (Bs [1] будет концом первого элемента B).
Если у вас множественное виртуальное наследование и компилятору нужно изменить указанный адрес, то это будет сделано только для первого элемента.
Так :
Ничего не нужно копировать, только адрес указателя первого элемента будет изменен при необходимости.
Да, просто добавьте несколько элементов в B, чтобы B было больше, чем A, и вы оказались в прекрасном мире с неопределенным поведением. Вы также можете скопировать часть A объекта C внутри объекта B.
Nb: Если вы хотите управлять некоторыми B и C в одном массиве, вы можете использовать A ** (или std :: любой), и тогда это будет безопасно. (Но вы, вероятно, уже знаете это).
1) да, vptr
не копируется в оператор присваивания. Оператор присваивания не меняет тип времени выполнения объекта.
2) A* bs = new B[5];
является опасно. Что, если B
содержит некоторые данные, которые A
не делает? затем sizeof(B) > sizeof(A)
и доступ к элементам через bs[i]
вызывает неопределенное поведение (ошибка сегментации в моей системе).