Operator dynamic_cast
Konwertuje argument operacji expression do obiektu typu type-id.
dynamic_cast < type-id > ( expression )
Uwagi
type-id musi być wskaźnikiem lub odwołaniem do poprzednio zdefiniowanego typu klasy lub "wskaźnikiem do void".Typ expression musi być wskaźnikiem, jeśli type-id jest wskaźnikiem lub l-wartością, jeśli type-id jest odwołaniem.
Zobacz statyczne rzutowanie, aby znaleźć wyjaśnienie różnicy między statycznymi i dynamicznymi konwersjami rzutowania, a także kiedy właściwe jest ich wykorzystanie.
Istnieją dwie najświeższe zmiany w zachowaniu dynamic_cast w kodzie zarządzanym:
dynamic_cast wskaźnika na podstawowy tabelaryczny typ wyliczeniowy zakończy się niepowodzeniem w czasie wykonywania, zwracając 0 zamiast przekonwertowanego wskaźnika.
dynamic_cast nie spowoduje zgłoszenia wyjątku, podczas gdy type-id jest wewnętrznym wskaźnikiem na typ wartości z rzutowaniem kończącym się niepowodzeniem w czasie wykonywania. Rzutowanie zwraca wartość 0 wskaźnika zamiast zgłaszania wyjątku.
Jeśli type-id jest wskaźnikiem do jednoznacznej, dostępnej bezpośrednio lub pośrednio klasy podstawowej expression, wynikiem jest wskaźnik do unikatowego podobiektu typu type-id.Na przykład:
// dynamic_cast_1.cpp
// compile with: /c
class B { };
class C : public B { };
class D : public C { };
void f(D* pd) {
C* pc = dynamic_cast<C*>(pd); // ok: C is a direct base class
// pc points to C subobject of pd
B* pb = dynamic_cast<B*>(pd); // ok: B is an indirect base class
// pb points to B subobject of pd
}
Ten typ konwersji nazywa się "przypisaniem elementu nadrzędnego", ponieważ przesuwa wskaźnik w górę hierarchii klas, od klasy pochodnej do klasy, z której się wywodzi.Przypisanie elementu nadrzędnego jest niejawną konwersją.
Jeśli type-id to void*, wykonywane jest sprawdzenie w czasie wykonania w celu określenia rzeczywistego typu expression.Wynik jest wskaźnikiem do kompletnego obiektu wskazywanego przez expression.Na przykład:
// dynamic_cast_2.cpp
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};
void f() {
A* pa = new A;
B* pb = new B;
void* pv = dynamic_cast<void*>(pa);
// pv now points to an object of type A
pv = dynamic_cast<void*>(pb);
// pv now points to an object of type B
}
Jeśli type-id to nie void*, wykonywane jest sprawdzenie w czasie wykonania, aby zobaczyć czy obiekt wskazywany przez expression może zostać skonwertowany na typ wskazywany przez type-id.
Jeśli typ expression jest klasą podstawową typu type-id, wykonywane jest sprawdzenie w czasie wykonania, aby zobaczyć czy expression faktycznie wskazuje na kompletny obiekt typu type-id.Jeśli to prawda, wynikiem jest wskaźnik do kompletnego obiektu typu type-id.Na przykład:
// dynamic_cast_3.cpp
// compile with: /c /GR
class B {virtual void f();};
class D : public B {virtual void f();};
void f() {
B* pb = new D; // unclear but ok
B* pb2 = new B;
D* pd = dynamic_cast<D*>(pb); // ok: pb actually points to a D
D* pd2 = dynamic_cast<D*>(pb2); // pb2 points to a B not a D
}
Ten typ konwersji nazywa się "przypisaniem elementu podrzędnego", ponieważ przesuwa wskaźnik w dół hierarchii klas z danej klasy do klasy z niej pochodzącej.
W przypadku wielokrotnego dziedziczenia, pojawia się możliwość wystąpienia niejednoznaczności.Należy wziąć pod uwagę hierarchię klas pokazaną na poniższym rysunku.
Dla typów CLR dynamic_cast skutkuje brakiem operacji, jeśli konwersja może zostać wykonana w sposób niejawny lub wykonaniem instrukcji MSIL isinst, która wykonuje dynamiczne sprawdzenie i zwraca nullptr jeśli konwersja się nie powiodła.
Następujące przykłady używają dynamic_cast do określenia, czy klasa jest wystąpieniem określonego typu:
// dynamic_cast_clr.cpp
// compile with: /clr
using namespace System;
void PrintObjectType( Object^o ) {
if( dynamic_cast<String^>(o) )
Console::WriteLine("Object is a String");
else if( dynamic_cast<int^>(o) )
Console::WriteLine("Object is an int");
}
int main() {
Object^o1 = "hello";
Object^o2 = 10;
PrintObjectType(o1);
PrintObjectType(o2);
}
Hierarchia klas pokazująca wielokrotne dziedziczenie
Wskaźnik do obiektu typu D może być bezpiecznie rzutowany na B lub C.Jednakże jeśli D jest rzutowane na wskaźnik do obiektu A, które wystąpienie A będzie wynikiem?Spowodowałoby to błąd rzutowania niejednoznacznego.Aby obejść ten problem, można wykonać dwa jednoznaczne rzutowania.Na przykład:
// dynamic_cast_4.cpp
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};
class D : public B {virtual void f();};
void f() {
D* pd = new D;
B* pb = dynamic_cast<B*>(pd); // first cast to B
A* pa2 = dynamic_cast<A*>(pb); // ok: unambiguous
}
Korzystanie z wirtualnych klas podstawowych, może spowodować dalsze niejednoznaczności.Należy wziąć pod uwagę hierarchię klas pokazaną na poniższym rysunku.
Hierarchia klas pokazująca wirtualne klasy podstawowe
W tej hierarchii A jest wirtualną klasą podstawową.Zobacz Wirtualne klasy podstawowe, aby zapoznać się z definicją wirtualnej klasy podstawowej.Biorąc pod uwagę wystąpienie klasy E i wskaźnik do podobiektu A, dynamic_cast wskaźnika do B zakończy się niepowodzeniem z powodu niejednoznaczności.Najpierw należy rzutować z powrotem do kompletnego obiektu E, a następnie przejść w górę hierarchii w sposób jednoznaczny, aby dotrzeć do poprawnego obiektu B.
Należy wziąć pod uwagę hierarchię klas pokazaną na poniższym rysunku.
Hierarchia klas pokazująca zduplikowane klasy podstawowe
Podany obiekt typu E i wskaźnik do podobiektu D, aby przejść z podobiektu D do pierwszego z lewej podobiektu A mogą wykonać trzy konwersje.Można wykonać konwersję dynamic_cast z wskaźnika D do wskaźnika E, a następnie konwersję (dynamic_cast albo niejawną konwersję) z E do B i w końcu niejawną konwersję z B do A.Na przykład:
// dynamic_cast_5.cpp
// compile with: /c /GR
class A {virtual void f();};
class B : public A {virtual void f();};
class C : public A { };
class D {virtual void f();};
class E : public B, public C, public D {virtual void f();};
void f(D* pd) {
E* pe = dynamic_cast<E*>(pd);
B* pb = pe; // upcast, implicit conversion
A* pa = pb; // upcast, implicit conversion
}
Operator dynamic_cast może również zostać użyty do przeprowadzenia "rzutowania krzyżowego". Korzystając z tej samej hierarchii klasy, można rzutować wskaźnik, na przykład z podobiektu B do podobiektu D, tak długo, jak długo kompletny obiekt jest typu E.
Biorąc pod uwagę rzutowania krzyżowe, możliwe jest wykonanie konwersji wskaźnika do D na wskaźnik do pierwszego z lewej podobiektu A w zaledwie dwóch krokach.Można wykonać rzutowanie krzyżowe z D do B, a następnie niejawną konwersję z B do A.Na przykład:
// dynamic_cast_6.cpp
// compile with: /c /GR
class A {virtual void f();};
class B : public A {virtual void f();};
class C : public A { };
class D {virtual void f();};
class E : public B, public C, public D {virtual void f();};
void f(D* pd) {
B* pb = dynamic_cast<B*>(pd); // cross cast
A* pa = pb; // upcast, implicit conversion
}
dynamic_cast konwertuje wartość pustego wskaźnika do wartości pustego wskaźnika typu docelowego.
Gdy użytkownik wykorzystuje dynamic_cast < type-id > ( expression ), jeśli expression nie może być bezpiecznie przekonwertowane na typ type-id, sprawdzenie w czasie wykonania spowoduje zakończenie rzutowania niepowodzeniem.Na przykład:
// dynamic_cast_7.cpp
// compile with: /c /GR
class A {virtual void f();};
class B {virtual void f();};
void f() {
A* pa = new A;
B* pb = dynamic_cast<B*>(pa); // fails at runtime, not safe;
// B not derived from A
}
Wartością nieudanego rzutowania na typ wskaźnika jest pusty wskaźnik.Nieudane rzutowanie na typ referencyjny zgłasza wyjątek bad_cast. Jeśli expression nie wskazuje na prawidłowy obiekt, ani się do niego nie odwołuje, zgłaszany jest wyjątek __non_rtti_object.
Zobacz typeid, aby zapoznać się z wyjaśnieniem wyjątku __non_rtti_object.
Przykład
Poniższy przykład tworzy wskaźnik klasy podstawowej (struct A) do obiektu (struct C). To, plus fakt istnienia funkcji wirtualnych, umożliwia działanie polimorfizmu w trakcie wykonania.
Przykład wywołuje również niewirtualną funkcję w hierarchii.
// dynamic_cast_8.cpp
// compile with: /GR /EHsc
#include <stdio.h>
#include <iostream>
struct A {
virtual void test() {
printf_s("in A\n");
}
};
struct B : A {
virtual void test() {
printf_s("in B\n");
}
void test2() {
printf_s("test2 in B\n");
}
};
struct C : B {
virtual void test() {
printf_s("in C\n");
}
void test2() {
printf_s("test2 in C\n");
}
};
void Globaltest(A& a) {
try {
C &c = dynamic_cast<C&>(a);
printf_s("in GlobalTest\n");
}
catch(std::bad_cast) {
printf_s("Can't cast to C\n");
}
}
int main() {
A *pa = new C;
A *pa2 = new B;
pa->test();
B * pb = dynamic_cast<B *>(pa);
if (pb)
pb->test2();
C * pc = dynamic_cast<C *>(pa2);
if (pc)
pc->test2();
C ConStack;
Globaltest(ConStack);
// will fail because B knows nothing about C
B BonStack;
Globaltest(BonStack);
}