С++ - повышение и понижение

В моем примере:

При восходящем приведении не должен ли второй вызов d.print() печатать «базу»?

Разве это не производный объект "d", преобразованный в объект базового класса?

А при даункастинге какие у него преимущества?

Не могли бы вы объяснить восходящее и нисходящее на практике?

#include <iostream>
using namespace std;

class Base {
public:
    void print() { cout << "base" << endl; }
};

class Derived :public Base{
public:
    void print() { cout << "derived" << endl; }

};

void main()
{
    // Upcasting
    Base *pBase;
    Derived d;
    d.print();
    pBase = &d;
    d.print();

    // Downcasting
    Derived *pDerived;
    Base *b;
    pDerived = (Derived*)b;
}

person Mihai    schedule 30.01.2016    source источник
comment
Как вы думаете, почему строка pBase должна изменить поведение строки d.print();? Вы хотели спросить о pBase->print(); ?   -  person M.M    schedule 01.02.2016


Ответы (2)


Восходящее приведение неявно реализовано в C++ и часто используется, когда вы имеете дело с виртуальной диспетчеризацией. Другими словами, у вас есть указатель на Base, из которого вы можете получить доступ к общему интерфейсу всей иерархии классов, а выбор можно сделать во время выполнения. Это предполагает, что ваши функции интерфейса помечены virtual. Пример:

Base* pBase; 
cin >> x; 
if(x == 0) // this is done at runtime, as we don't know x at compile time
    pBase = new Derived1;
else
    pBase = new Derived2;

pBase->draw(); // draw is a virtual member function

Это чрезвычайно полезно в таких ситуациях, когда диспетчеризация выполняется во время выполнения. Проще говоря, восходящее приведение позволяет рассматривать производный класс как базовый класс (через его общий интерфейс).

Нисходящее приведение менее полезно, и ИМО следует избегать, когда это возможно. В общем, это признак плохого дизайна, так как редко нужно преобразовывать объект Base в производный. Это можно сделать (и проверить результат) через dynamic_cast, например

Base* pBase = new Derived; // OK, the dynamic type of pBase is Derived
Derived* pDerived = dynamic_cast<Derived*>(pBase);
if(pDerived) // always test  
{
    // success
}
else
{
    // fail to down-cast
}

Эта ссылка представляет собой весьма полезное введение в тему.

person vsoftco    schedule 30.01.2016
comment
Итак, строки кода: pBase = new Derived1; и pBase = новый Derived2; представляют на самом деле upcasting ? Если это так, предположим, что x равно 0, а pBase будет указывать на объект класса Derived1. Какая разница, если указатель pBase будет Derived1 *pBase ? - person Mihai; 30.01.2016
comment
@Mihai Это полезно, когда вы заранее не знаете производный тип. В этом случае вы используете указатель на базу для управления производной иерархией. - person vsoftco; 30.01.2016
comment
Низкое литье — плохой дизайн? Это безумие! Плохой дизайн — это преобразование вверх, поскольку оно приводит к неопределенному поведению, неизвестному во время компиляции и непредсказуемому во время выполнения. На самом деле это ниспровергающий пример, который обеспечивает прочную основу для работы. - person Poriferous; 30.01.2016
comment
dynamic_cast<Derived> должно быть dynamic_cast<Derived *>. - person Mihai Todor; 30.01.2016
comment
@Poriferous Надеюсь, вы понимаете, что Base* p = new Derived (неявно) повышает, а не понижает. Upcasting используется гораздо чаще, чем downcasting. Опять же, ИМО, dynamic_cast следует использовать редко. - person vsoftco; 30.01.2016
comment
Имо, это static_cast, который нужно использовать редко. dynamic_cast часто используется и обеспечивает безопасный способ преобразования одного типа в другой. Когда дело доходит до приведения базы к производной, нужно использовать определенные функции этого производного, которые в противном случае раздули бы или нарушили базовый интерфейс. Возможно, вы слишком широко смотрите на вещи или смотрите на дизайн с технической точки зрения, а не с философской. - person Poriferous; 30.01.2016
comment
@Poriferous Я согласен, что нельзя static_cast использовать динамические типы. Однако повышение приведения неявно, это просто управление интерфейсом иерархии классов через базовый указатель. А это важно имхо. - person vsoftco; 30.01.2016
comment
Кстати, ОП не использовал виртуальные методы, что не позволит ему использовать виртуальную диспетчеризацию. - person Mihai Todor; 30.01.2016
comment
В качестве дополнения, есть некоторые ситуации, когда приведение вниз является вполне допустимым выбором, а не плохим дизайном. В первую очередь это случаи, когда фактический тип известен при использовании (и обычно предназначен для предоставления доступа ко всему интерфейсу (если применимо), а не только к общей части) или скрыт внутри самого класса. Первый может возникнуть, когда объект должен быть передан через код, которому не нужно знать его фактический тип между созданием и потреблением, а второй позволяет базе проверить совместимость перед отправкой (например, для полиморфных операторов сравнения). - person Justin Time - Reinstate Monica; 19.09.2019

Вам необходимо использовать виртуальные методы, чтобы включить RTTI.

В вашем случае, поскольку вы используете C++, вам следует полагаться на более безопасные механизмы литья. Итак, вместо (Derived*)b вы должны использовать dynamic_cast<Derived*>(b). Это позволяет убедиться, что вы действительно владеете указателем на объект базового класса (интерфейса), который был получен приведением объекта типа Derived. Эта страница содержит дополнительные пояснения.

person Mihai Todor    schedule 30.01.2016
comment
Я не против (может быть, я должен ..), но ваш ответ довольно хороший ответ, но не на этот самый вопрос выше - я думаю, в этом причина. - person PiotrNycz; 10.04.2018
comment
Что ж, в свою защиту, вопрос тоже был не таким уж замечательным. Пожалуйста, дайте мне знать, как, по вашему мнению, я должен улучшить свой ответ. - person Mihai Todor; 10.04.2018