Как работает 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;
}