Множественное наследование в с++

155
22 мая 2019, 09:40

Прохожу курс и застрял на задаче, которую в упор не могу понять. Ниже само задание, которое не нужно решать. Мне непонятно как это работает и прошу пояснений.

Вам дана следующая иерархия классов:

struct Base { ... }; 
struct D1 : Base { ... };
struct D2 : Base { ... }; 
struct D3 : D1, D2 { ... };

Напишите функцию D1BaseToD2Base, которая преобразует указатель типа Base на объект типа D3, который ссылается на экземпляр Base, соответствующий D1, в указатель, ссылающийся на экземпляр Base соответствующий D2.

// base указывает на экземпляр Base, соответствующий D1
// нужно вернуть указатель на экземпляр Base, соответствующий D2
Base const * D1BaseToD2Base( Base const * base )
{
    return ...
}

Дальше ход моих рассуждений:

1)Указатель типа Base, который указывает на объект типа D3, который в свою очередь ссылается на элземпляр Base, который указывает на D1:

D1 D1_obj = new D1;
Base *Base_D1 = & D1_obj;
D3 *B3_obj = & Base_D1;
Base *base = & B3_obj; // тот самый base, что находится в прототипе нужной функции.

2)Должно ссылаться на экземпляр Base соответствующий D2:

D2 D2_obj = new D2;
Base *New_base = &D2_obj;

В чём моя ошибка представления такой иерархии классов? И так ли работают в данном случае конструкции ссылки одного класса на другой? Буду благодарен, если кто-то приведёт ясный пример подобной иерархии классов с соответствующими преобразованиями указателей. Повторюсь, что решение приведённой задачи не подразумевается в вопросе.

Answer 1

Приведеное задание на тему ромбовидного наследования. Чтобы проще всего объяснить о каком указателе идет речь в задании, нужно немного порисовать:

Base = {члены Base}
D1 = {{члены Base} + члены D1}
D2 = {{члены Base} + члены D2}
D3 = {{{члены Base} + члены D1} + {{члены Base} + члены D2} + члены D3}
       ^                           ^
       |- исходный указатель       |- требуемый указатель

На этой схеме фигурными скобками показаны границы объектов, то есть то на что можно получить указатель

В D3 содержится две реализации Base, одна наследовалась из D1, а вторая из D2. Понимаете, наследование в Си++ нужно рассматривать не только как отношение "является", но и как отношение "содержит". Вот это "содержит" является, скажем так, особенностью реализации наследования в Си++.

Так вот, в задании речь идет об указателе на Base который наследовался из D1. Этот указатель нужно привести к указателю на второй Base, который наследовался из D2.

Общее правило преобразование в Си++ такое: оператор приведения не нужен когда вы преобразуете от внешних скобок к внутренним из одной ветки; в остальных случаях нужно использовать специальные операторы приведения, а именно когда:

  • приводится общий тип к частному, то есть от вложенных скобок к внешним
  • когда типы находятся в разных ветках
  • когда один тип находится в нескольких ветках, тут возникает неопределенность (ваш случай).
Answer 2

Вам дана следующая иерархия классов:

struct Base { ... };

struct D1 : Base { ... };

struct D2 : Base { ... };

struct D3 : D1, D2 { ... };

Напишите функцию D1BaseToD2Base, которая преобразует указатель типа Base на объект типа D3, который ссылается на экземпляр Base, соответствующий D1, в указатель, ссылающийся на экземпляр Base соответствующий D2.

Похоже, что без RTTI это задание сделать нельзя.

Если Вы имеете указатель на Base в экземпляре D3, который указывает на D1 из состава D3, то Вы не можете получить указатель даже на сам D1 из состава D3.
Тем более Вы не можете получить указатель на D3, чтобы потом привести его к указателю на D2 из состава D3.

В норме (без RTTI) можно получить только указатель на базовый класс, имея указатель на производный класс.
Обратное без RTTI невозможно, то есть без RTTI невозможно получить указатель на производный класс имея указатель на базовый класс.

UPD1:

"Обратное без RTTI невозможно" - да запросто, другое дело, что не будет рантайм проверки. – VTT 1 минуту назад

Как это "запросто"? Мой мир рухнул!
И что это за рантайм проверка?

UPD2:

@pepsicoca1 Задайте свой вопрос, если интересно – Cerbo 5 секунд назад

Вопрос о чем?
Как получить указатель на производный класс, имея указатель на базовый класс?
Никак нельзя получить без RTTI, это я и так знаю.

UPD3:

Вот как выглядит решение задачи с использованием RTTI (то есть с использованием dynamic_cast):

#include "pch.h"
#include <iostream>
using namespace std;
class Base {
public:
    virtual void fun() {};
    void printid() { cout << endl << "this is base id"; }
};
class D1 : public Base {
public:
    void printid() { cout << endl << "this is d1 id"; };
};
class D2 : public Base {
public:
    void printid() { cout << endl << "this is d2 id"; };
};
class  D3 : public D1, D2 {
public:
    void printid() { cout << endl << "this is d3 id"; };
};
    // base указывает на экземпляр Base, соответствующий D1
    // нужно вернуть указатель на экземпляр Base, соответствующий D2
Base* D1BaseToD2Base(Base* base) {
    D1* d1_ptr = dynamic_cast<D1*>(base);
    D3* d3_ptr = dynamic_cast<D3*>(d1_ptr);
    D2* d2_ptr = (D2*)d3_ptr;
    Base* base_ret = (Base*)d2_ptr;
    base->printid();
    d1_ptr->printid();
    d3_ptr->printid();
    d2_ptr->printid();
    base_ret->printid();
    return base_ret;
}
int main() {
    std::cout << endl << "Hello World!\n";
    D3 d3;
    D1* d1_ptr = (D1*)(&d3);
    Base* base_ptr = (Base*)d1_ptr;
    D1BaseToD2Base(base_ptr);
    return 0;
}

Результат работы этой программы такой:

Hello World!
this is base id
this is d1 id
this is d3 id
this is d2 id
this is base id

Проверялось на VS2017.

READ ALSO
#define с++ для чего служит

#define с++ для чего служит

например код:

181
undefined reference to `vtable for ClassName&#39;

undefined reference to `vtable for ClassName'

Возникли ошибки при компиляции"qmake" ругается:

151