Хобрук: Ваш путь к мастерству в программировании

Когда на практике полезно использовать dynamic_cast от родителя к потомку? Всегда ли это плохая практика?

Когда на практике полезно использовать dynamic_cast от родителя к потомку? Всегда ли это плохая практика?

Это НЕ дубликат: Полиморфизм C ++: от родительского класса к дочернему Информация о типах времени выполнения в C ++

Сначала давайте начнем с примера кода, а затем сделаем некоторые выводы.

#include<iostream> 
using namespace std; 
class B { public: virtual void fun() {cout<<"!class B!\n";} }; 
class D: public B { public: void fun() {cout<<"!class D!\n";} }; 

int main() 
{ 
/////////   1. Dynamic initialization parent from child

B *bAux = new D; 
D *d = dynamic_cast<D*>(bAux); 
cout<<"1.1. for d is "; d->fun();   //class D

B *b = dynamic_cast<B*>(d);
cout<<"1.2. for b is "; b->fun();   //class D

/////////   2. Dynamic initialization child from parent

//D *dAux4 = new B;  //invalid conversion from ‘B*’ to ‘D*’

/////////   3. Casting the parent to child

B *bAux2 = new B;
cout<<"3.1. for bAux2 is "; bAux2->fun();   //class B

D *d2 = dynamic_cast<D*>(bAux2); 
if (d2 != NULL){ cout<<"3.2. for d2 is "; d2->fun(); }  //cannot cast parent to child
else cout<<"3.2. cannot cast B* to D* \n";

/////////   4. Casting the child to parent

D *dAux3 = new D;
cout<<"4.1. for dAux3 is "; dAux3->fun();   //class D

B *b3 = dynamic_cast<B*>(dAux3); 
cout<<"4.2. for b3 is "; b3->fun();   //class D

getchar(); 
return 0; 
} 

Во-вторых, подведем итоги. Результат:

1.1. for d is !class D!                                                                                                                      
1.2. for b is !class D!                                                                                                                      
3.1. for bAux2 is !class B!                                                                                                                  
3.2. cannot cast B* to D*                                                                                                                    
4.1. for dAux3 is !class D!                                                                                                                  
4.2. for b3 is !class D!

Но для случая 3 мы фактически пытаемся выполнить динамическое приведение из B * в D * (как уже упоминалось, это невозможно). Интересно, в каких случаях это полезно? Вы можете нарушить приведение типов (в качестве параллели, в случае проверки статического типа, возникает хорошо известная проблема: поскольку медведь - это животное, не должен ли набор медведей быть набором животных? Нет, потому что затем можно вставить Волка в набор и получить Волка среди Медведей).

Итак, вопрос в том, в каком случае полезно динамическое преобразование от родителя к потомку? Это плохая практика?

РЕДАКТИРОВАТЬ: после обсуждения конкретный случай:

/////////   5. Try to cast the parent initialized from a child to another child

B *bAux = new D; 
F *f = dynamic_cast<F*>(bAux); 
if(f == NULL)
    cout<<"5.1. Cannot cast the parent initialized from a child to another child \n";// f->fun();   //segmentation fault

B *b = dynamic_cast<B*>(f);
if(b == NULL)
    cout<<"5.2. Cannot cast the NULL pointer back to parent \n"; //b->fun();   //segmentation fault

  • Я всегда стараюсь избегать этого, это может привести к случайному доступу к несуществующим членам в родительском B и определенным в дочернем D. 11.01.2021
  • Да, такое тоже может случиться, хотя компилятор нам помогает. 11.01.2021
  • @kaileena весь смысл dynamic_cast в том, что он завершится ошибкой, если объект на самом деле не является дочерним типом D. Вы не сможете получить доступ к этим несуществующим членам. 11.01.2021
  • Итак, вопрос в том, в каком случае полезно динамическое преобразование от родителя к потомку? Это плохая практика? Шаблон ациклического посетителя требует этого, например, но в довольно общем виде, а не в качестве замены переключателя. 11.01.2021
  • @kaileena Вы имели в виду возможные недостатки соответствующего static_cast, а не dynamic_cast! 11.01.2021
  • Возможно, я неправильно читаю, но вы говорите: «Но для случая 3 мы действительно можем динамическое преобразование из B * в D *, но ваш код этого не показывает. Он выводит 3.2. cannot cast B* to D* 11.01.2021
  • @Kevin - выводит 3.2. не может преобразовать B * в D *, как я показываю в вопросе 11.01.2021
  • @ user3742309, который не согласен с вашим утверждением, но для случая 3 мы действительно можем динамически преобразовать B * в D *, не так ли? 11.01.2021
  • Я редактирую вопрос, теперь должно быть более ясно 11.01.2021

Ответы:


1

dynamic_cast может быть полезен в случае, если вы хотите получить интерфейс производных объектов: если есть f () в производном и нет функции f () в базе, вы не можете вызвать f () через B *. но вы можете использовать dynamic_cast для D * и вызвать f () (если B * указывает на объект D). (Помня, что dynamic_cast предназначен для полиморфных типов) Рассмотрим следующий код:

class B {
public:
    virtual void g()
    {
        std::cout << "Base::g()\n";
    }
};
class D : public B
{
public:
    void f()
    {
        std::cout << "f()\n";
    }
    void g()
    {
        std::cout << "Derived::g()\n";
    }
};
int main()
{
    B* p = new D;
    //p->f(); // class B has no member f
    D* pd = dynamic_cast<D*>(p);
    pd->f(); // ok f() will be called

    return 0;
}
11.01.2021
  • Использован хороший пример. С архитектурной точки зрения, если в B не определена функция f (), скорее всего, предполагается, что объект B должен иметь функциональность f (). Это может привести к функциональному неопределенному поведению. Технически я показал, что это действительно возможно. 12.01.2021
  • Например, круг, полученный из рисунка. Почему я должен преобразовать объект Figure в объект Circle, если нужно рассчитать поверхность круга? Этот объект Figure впоследствии может быть неправильно истолкован или использован. 12.01.2021
  • Привет, Вот реалистичный пример, в котором вы должны привести к дочернему элементу: у вас есть база транспортного средства и, возможно, производные классы (Mercedes, BMW и т. Д.), А также производный класс Tesla. Класс Tesla может иметь функцию: setAutoDriveMode () и, если у вас есть Vehicle * p = new Tesla {}; вы должны преобразовать p в T *, чтобы вызвать setAutoDriveMode (). Вам не следует добавлять setAutoDriveMode () в base: Vehicle, потому что не все автомобили имеют такую ​​функциональность. 13.01.2021
  • dynamic_cast полезен в следующих случаях:. вы не можете добавить виртуальную функцию к базовому классу, потому что базовый класс взят из библиотеки. . вам нужен доступ к чему-то, что зависит от производного класса. У вас нет возможности добавить эту виртуальную функцию в базовый класс, потому что другие производные классы не нуждаются и не имеют этой функциональности. 13.01.2021
  • Спасибо за ответ. 1 относительно автомобиля например: Я считаю плохим выбором использовать автомобиль * p = new Tesla {}; вместо непосредственного создания Tesla * p = new Tesla (); Потому что вы можете попробовать преобразовать Vehicle * p в BMW, а затем использовать специальный метод BMW для автомобиля, предназначенного для Tesla. Надеюсь, в C ++ это не работает. Здесь нам помогает ошибка сегментации, я обновлю вопрос 2. Что касается библиотеки классов, я действительно вижу этот случай, когда у вас есть эти архитектурные ограничения. 14.01.2021

  • 2

    Один из примеров - это когда существует несколько базовых классов одного и того же типа. Только dynamic_cast может понижать неуникальный базовый класс до наиболее производного:

    struct A { virtual ~A() = 0; };
    struct B1 : A {};
    struct B2 : A {};
    struct C : B1, B2 {};
    C& f(A& a) { return dynamic_cast<C&>(a); } // Only dynamic_cast can cast A& to C&.
    

    Другой пример: только dynamic_cast<void*> может вернуть указатель на адрес всего объекта, никакое другое приведение не может.

    11.01.2021
  • Да, это представляет собой алмазный полиморфизм, но мне интересно, когда это полезно? В каких случаях вам нужно перейти от базового класса к наиболее производному? 11.01.2021
  • @ user3742309 Это не алмазный полиморфизм. Полиморфизм алмаза требует виртуальных базовых классов. 11.01.2021
  • Хорошо, я заметил, что 11.01.2021
  • Новые материалы

    Разработчики — Избегайте сложностей глупо
    Сложность управляется, а не побеждается «Простота — великая добродетель, но для ее достижения требуется тяжелая работа и образование, чтобы оценить ее. И что еще хуже: сложность продается..

    Как сделать HTML динамическим с помощью JavaScript
    Код JavaScript выполняется внутри страниц сайта. Таким образом, страница вашего сайта содержит метки HTML, а также пояснения (скрипты), составленные с использованием диалекта сценариев, такого как..

    Деревья классификации и регрессии
    Это мой второй пост об алгоритмах машинного обучения. Мой первый пост посвящен искусственным нейронным сетям, вы можете найти его ниже. Нейронные сети — базовое..

    HMTL - Многозадачное обучение для решения задач НЛП
    Достижение результатов SOTA путем передачи знаний между задачами Область обработки естественного языка включает в себя десятки задач, среди которых машинный перевод, распознавание именованных..

    Решения DBA Metrix
    DBA Metrix Solutions предоставляет удаленного администратора базы данных (DBA), который несет ответственность за внедрение, обслуживание, настройку, восстановление базы данных, а также другие..

    Начало работы с Блум
    Обзор и Codelab для генерации текста с помощью Bloom Оглавление Что такое Блум? Некоторые предостережения Настройка среды Скачивание предварительно обученного токенизатора и модели..

    Создание кнопочного меню с использованием HTML, CSS и JavaScript
    Вы будете создавать кнопочное меню, которое имеет состояние наведения, а также позволяет вам выбирать кнопку при нажатии на нее. Финальный проект можно увидеть в этом Codepen . Шаг 1..