#cpp #функции #наследование
1) Пытаюсь запустить такой код: #includestruct 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
Дочерний объект содержит в себе родительский объект, поэтому приведение дочернего объекта к родительскому объекту не вызывает проблем и может быть осуществлено неявно. С другой стороны, родительский объект ничего не знает о том, какие и как могут быть определены дочерние от него объекты. Он такой информацией не обладает. А потому нет неявного преобразования из родительского объекта в какой-либо дочерний. Когда нет виртуальных функций, то происходит статическое связывание вызываемых функций с типом объекта на этапе компиляции. В этой программе #includestruct 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! Я прозрел! Код ниже выводит ВВ, как и типа ожидалось #includeclass 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; }