Как работает dynamic_cast?

Если у вас было следующее:

class Animal{};

class Bird : public Animal{};

class Dog : public Animal{};

class Penguin : public Bird{};

class Poodle : public Dog{};

Только ли dynamic_cast проверяет, является ли один класс производным классом другого, или один класс является базовым классом другого? Так что, если бы у меня было:

Bird* bird;
Animal* animal;

bird = dynamic_cast<Animal*>(bird);
animal = dynamic_cast<Bird*>(animal);

bird теперь указывает на класс Animal, так что я могу использовать bird->some_function(); и это будет вызывать функцию в Animal? А animal теперь указывает на класс Bird, так что я могу сделать animal->some_function(); и это вызовет some_function(); в Bird?

Я пытался выяснить, как работает dynamic_cast, и ресурсы, которые я нашел в Интернете, были не самыми полезными. Если кто-то может предложить другое понимание функциональности dynamic_cast и некоторых примеров, в которых это было бы полезно, я был бы очень признателен.

Ответ 1

Самое важное в динамическом приведении - то, что оно должно применяться к polymorphic type. Без этого динамическое приведение работает как статическое приведение.

Что такое полиморфный тип? Любой класс, в котором есть хотя бы один виртуальный метод, виртуальный деструктор или виртуальный базовый класс, является полиморфным. Только эти типы имеют virtual method table (VMT) в своей структуре данных. Классы, которые не имеют ничего виртуального, не имеют VMT. Стандарт не говорит о том, как следует реализовывать полиморфизм и виртуальные методы, но все компиляторы, насколько я знаю, делают это.

В ваших примерах классы не полиморфны. На мой взгляд, было бы лучше, если бы компиляторы выдавали ошибку, когда динамическое приведение применяется к неполиморфному типу. Тем не менее, они этого не делают. Это добавляет путаницы.

Указатели VMT для всех классов разные. Это означает, что во время выполнения, глядя на:

Animal* animal;

можно узнать, каков реальный класс объекта. Это Bird или Dog или что-то еще. Зная реальный тип из значения VMT, сгенерированный код может внести корректировку, если это необходимо.

Вот пример:

class Animal   { virtual ~Animal();   int m1; };
class Creature { virtual ~Creature(); int m2; };

class Bird : public Animal, Creature { };

Bird   *bird = new Bird();
Creature *creature = dynamic_cast<Creature*>(bird);

Обратите внимание, что существо не первый базовый класс. Это означает, что указатель будет смещен, чтобы указывать на правую часть объекта. Тем не менее, следующие будут работать:

Animal *animal = dynamic_cast<Animal*>(creature);   // Case2.

потому что VMT Существа, когда он является частью другого класса, не будет таким же, как VMT объекта, когда он используется отдельно:

Creature *creature1 = new Creature();

Это различие позволяет правильно реализовать динамическое приведение. В примере Case2 указатель будет смещен назад. Я проверял это. Это работает.

Ответ 2

Оператор dynamic_cast проверяет тип фактического объекта, на который указывает указатель. Это то, что отличает его от времени компиляции static_cast; результат dynamic_cast зависит от данных времени выполнения.

dynamic_cast<Animal*>(bird)

В приведенном выше случае Animal является суперклассом Bird, поэтому dynamic_cast здесь не требуется (и компилятор будет относиться к нему так же, как static_cast или вообще не использовать).

dynamic_cast<Bird*>(animal)

В этом случае, когда этот оператор фактически выполняется, система времени выполнения проверяет фактический тип любого объекта Animal, который фактически указывает. Это может быть Bird или подкласс Bird, и в этом случае результат будет действительным Bird*. Если объект не является Bird, тогда результат будет NULL.

Ваш вопрос еще более усложняется тем фактом, что вы назначаете результат этих dynamic_cast обращений к исходному указателю. Возможно, это место, из-за которого возникает путаница, и я проигнорировал этот аспект из приведенного выше обсуждения.

Ответ 3

Это не имеет большого смысла, как вы выразились.

Точкой dynamic_cast является разрешение полиморфизма во время выполнения. Таким образом, реальный интересный сценарий будет похож на

void animalhandler(Animal& animal);

который, однако, не (по крайней мере, не только) вызван с экземплярами Animal, но с любым из подклассов. Вам часто даже не нужно знать: вы можете вызывать любых виртуальных членов Animal и быть уверенным, что С++ вызывает правильную перегрузку, для чего действительно принадлежит производный класс *animal.

Но иногда вы хотите сделать что-то, что возможно только с одним конкретным производным экземпляром. В этом случае вы используете dynamic_cast, например

void animalhandler(Animal& animal) {
  if(auto as_bird = dynamic_cast<Bird*>(&animal)) {
    // bird-specific code
  }
}

где if запускается только в том случае, если Animal на самом деле является Bird (или получен из Bird), в противном случае dynamic_cast просто возвращает nullptr, который if интерпретирует как false.

Теперь вы придумали идею сделать наоборот. Посмотрим, как это будет выглядеть:

  if(auto as_bird = dynamic_cast<Bird*>(&animal)) {
    if(auto as_animal = dynamic_cast<Animal*>(as_bird)) {
      // animal-specific code
    }
  }

... подождите, значит ли это что-то конкретное для животных? Нет, потому что все Bird являются Animal s, мы знаем, что во время компиляции там нет точки, проверяющей ее динамически. Вы все равно можете написать его, но вы можете также оставить его и использовать as_bird напрямую, так как он дает доступ ко всем членам, которые as_animal будет.

Ответ 4

Из рабочей страницы С++

Динамический литье [expr.dynamic.cast]

1 Результат выражения dynamic_cast <T> (v) является результатом преобразования выражения v в тип T. T должен быть указателем или ссылкой на полный тип класса или "указатель на cv void". Оператор dynamic_cast не должен отбрасывать константу (5.2.11).

6 В противном случае v должен быть указателем на или значением полиморфного типа (10.3).

8 Если C - тип класса, к которому T указывает или ссылается, проверка времени выполнения логически выполняется следующим образом:
- Если в самом производном объекте, указанном (указанном) на v, v указывает (ссылается) на подобъект публичного базового класса объекта C, и если только один объект типа C получается из подобъекта, указанного (связанного) с по v точки результата (относится) к этому объекту C.. - В противном случае, если v указывает (относится) к подобъекту общедоступного базового класса самого производного объекта, а тип самого производного объекта имеет базовый класс типа C, который является однозначным и общедоступным, точки результата (ссылаются) к подобъекту C самого производного объекта.
- В противном случае проверка времени выполнения не выполняется.

Что вы можете заключить из этих статей

  • dynamic_cast работает с полиморфными классами
  • он смотрит на время выполнения объекта, на который указывает (или ссылается) на
  • он решает на основе общедоступных базовых классов объекта, на который указывает, выполняется ли преследование или отсутствует

Ответ 5

Надеюсь, это поможет:

#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>

using namespace std;

class A{
public:
    A(){}
    virtual void write() const = 0;
};

class B : public A{
public:
    B(){}
    void write() const { cout << "I'm B" << endl; }
    void iam(){ cout << "Yes, I am" << endl; }
};

int main(){
    B b;

    A* a;
    a = &b;

    b.write();
    b.iam();

    a->write();
    //a->iam(); A don't have a method iam


    system("pause");
    return 0;
}