Повышающее преобразование динамического массива указателей на объекты наследников в базовый тип

Я пытаюсь разобраться, как передать в функцию, которая принимает динамический массив указателей на объекты базового класса, динамический массив указателей на объекты родительского класса. Я написал простой код, и не понимаю, почему он не работает. Передача указателя на объект родительского класса выполняется без проблем, то есть повышающее преобразование отрабатывает так, как нужно. Возможно, я не совсем правильно понимаю, как передавать, хотелось бы объяснений и/или исправлений. Спасибо.

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 шт):

Автор решения: user7860670

Дело в том, что такое преобразование указателя на один класс в указатель на другой класс (то же верно и для ссылок) возможно, когда эти классы находятся в одной иерархии. В примере 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);
}
→ Ссылка