Повышающее преобразование динамического массива указателей на объекты наследников в базовый тип
Я пытаюсь разобраться, как передать в функцию, которая принимает динамический массив указателей на объекты базового класса, динамический массив указателей на объекты родительского класса. Я написал простой код, и не понимаю, почему он не работает. Передача указателя на объект родительского класса выполняется без проблем, то есть повышающее преобразование отрабатывает так, как нужно. Возможно, я не совсем правильно понимаю, как передавать, хотелось бы объяснений и/или исправлений. Спасибо.
class A
{
public:
virtual void run() = 0;
};
class B: public A
{
public:
void run() override
{
}
};
void test(A** a, size_t s) // приём динамического массива указателей на объекты базового класса
{
for (size_t i(0); i < s; ++i)
(*(a + i))->run();
}
void test2(A* a) // приём указателя на объект базового класса
{
a->run();
}
int main()
{
B** a = new B * [10]; // создаем динамический массив указателей на объекты родительского класса
B* b = new B; // создаем указатель на объект родительского класса
test(a, 10); // попытка передать динамический массив указателей на объекты родительского класса: ERROR
test2(b); // передача указателя на объект родительского класса: ОК
return 0;
}
Интересуюсь в целях обучения.
Ответы (1 шт):
Дело в том, что такое преобразование указателя на один класс в указатель на другой класс (то же верно и для ссылок) возможно, когда эти классы находятся в одной иерархии. В примере B наследует от A, соответственно указатель на B можно преобразовать в указатель на A, причем в таком простом случае даже не надо будет модифицировать значение адреса. А вот типы B * и A * уже не родственные, и в общем случае так просто одно из другого не получить.
Проиллюстрирую на более наглядном примере:
struct A { float f; };
struct B { int i; };
struct D: B, A { };
Тут D наследует от A. Имея D * можно получить A * просто увеличив адрес на 4 байта.
Что же произойдет, когда у вас массив D *? Имея указатель на первый элемент этого массива D * * никакими преобразованиями адреса из него не удастся получить A * *. Для этого потребуется выделять новый массив указателей A * и конвертировать каждый из указателей D * из исходного массива.

Даже в простой ситуации, как в вашем примере, когда адреса указателей совпадают, от добавления перенаправления возникает еще один эффект, препятствующий их преобразованию:
class X: public A {}; // еще один класс
...
void testX(A** p_a)
{
// попытка присвоить X * указателю А * вполне правомерна
p_a[0] = new X{}; // ОК
}
int main()
{
B** p_b = new B * [10];
// попытка присвоить X * указателю B * очевидно ошибочна
p_b[0] = new X{}; // ERROR
// если бы это работало, то в массиве указателей B * легко мог бы оказаться X *
testX(p_b);
}
