Страницы

Поиск по вопросам

пятница, 21 февраля 2020 г.

Почему родительский объект не приводится к типу дочернего?

#cpp #функции #наследование


1) Пытаюсь запустить такой код:

#include 

struct A
{
    virtual void m() {
        std::cout << "A" ;
    }
};

struct B : public A
{
    void m() {
        std::cout << "B";
    }
};

int main()
{
    A a;
    B *b;

    b = &(B)a;
    b->m();

    return 0;
}


Если в main прописать:

int main()
{
    A *a;
    B b;

    a = &b;
    a->m();

    return 0;
}


то все ожидаемо работает. Но вот вопрос, почему нельзя привести тип родителя к типу
наследника, а наоборот можно? Как здесь работает механизм приведения типов? Или все
же можно привести тип родителя к типу наследника?

1) Еще такой вопрос, если написать так:

struct A
{
     void m() {
        std::cout << "A" ;
    }
};

struct B : public A
{
    void m() {
        std::cout << "B";
    }
};

int main()
{
    A *a;
    B b;

    a = &b;
    a->m();

    return 0;
}


то вызывается m() у обьекта A. Но объекта А как бы не существует. Мне не понятно,
где же тогда вызывается эта m(), у какого объекта типа A если обьекта типа A не существует?
    


Ответы

Ответ 1



Дочерний объект содержит в себе родительский объект, поэтому приведение дочернего объекта к родительскому объекту не вызывает проблем и может быть осуществлено неявно. С другой стороны, родительский объект ничего не знает о том, какие и как могут быть определены дочерние от него объекты. Он такой информацией не обладает. А потому нет неявного преобразования из родительского объекта в какой-либо дочерний. Когда нет виртуальных функций, то происходит статическое связывание вызываемых функций с типом объекта на этапе компиляции. В этой программе #include struct A { void m() { std::cout << "A" ; } }; struct B : public A { void m() { std::cout << "B"; } }; int main() { A *a; B b; a = &b; a->m(); return 0; } статическим типом указателя a является тип struct A, поэтому компилятор связывает функцию, объявленную в этом типе. На самом деле то же самое происходит и для виртуальных функций, то есть компилятор осуществляет поиск имени функции в соответствии со статическим типом указателя или ссылки на объект. Другое дело, что вызов функции осуществляется с помощью механизма, использующего таблицу виртуальных функций. Адрес этой таблицы разрешается динамически на этапе выполнения программы, хотя в некоторых простых случаях, когда статический и динамический типы совпадают, то может это делать на этапе компиляции.

Ответ 2



привидение родителя к наследнику выполнять разрешается и это называется downcast, которое можно делать, например, так b = static_cast(&a); А в вашем первом примере будет ошибка компиляции ввиду некорректного с точки зрения синтаксиса языка С++ кода b = &(B)a; вероятно, вы хотели написать так b = (B*)&a; edit ошибка компиляции будет, как отметил Ant, не с точки зрения синтаксиса а с точки зрения семантики языка С++ это каст в стиле С - которого лучше избегать, а в случае с downcast подавно - предпочтительнее применять специальные и более безопасные операторы явного приведения типов из С++ (как написано выше до этого) А вобще, подобный downcast в некоторых случаях может приводить к неопределённому поведению по поводу третьего вопроса - выводится A так как эта функция в родительском классе не виртуальна - в этом и основной смысл виртуальных функий - обеспечение вызова переопределённого в наследнике родительского метода через указатель на базовый класс (родительский класс), соответственно если в базовом классе функция не виртуальна то он и вызовется

Ответ 3



Спасибо, ampawd! Я прозрел! Код ниже выводит ВВ, как и типа ожидалось #include class A {public: virtual void m() { std::cout << "A" ; } }; class B : public A {public: void m() { std::cout << "B"; } }; int main() { A *a; B b; a = &b; a->m(); b.m(); return 0; }

Комментариев нет:

Отправить комментарий