Operator dynamicznego rzutowania (konwersji) stosuje się, gdy prawidłowość przekształcenia nie może być sprawdzona na etapie kompilacji, bo zależy od typu obiektu klasy polimorficznej, który znany jest dopiero w czasie wykonania. Jego użycie ma sens w sytuacjach, gdy mamy do czynienia z obiektami różnych klas powiązanych ze sobą hierarchią dziedziczenia.
Wyobraźmy sobie, że zdefiniowane są klasy A i dziedzicząca z niej klasa B. Klasy muszą być polimorficzne, a więc w klasie A musi istnieć choć jedna metoda wirtualna (patrz rozdział o dziedziczeniu ). Załóżmy, że istnieje też funkcja, której parametr ma typ A* lub A&. Czy można wywołać tę funkcję z argumentem typu B* lub B? Ponieważ obiekt klasy B można traktować jako obiekt klasy A, bo zawiera na pewno wszystkie składowe zadeklarowane w klasie A (i, być może, inne), więc takie wywołanie jest legalne i konwersja od typu B* do typu A* zajdzie niejawnie. Na marginesie zauważmy, że mówimy tu o przekazywaniu obiektów do funkcji przez wskaźnik lub referencję: nie przez wartość. Wartości typu B nie mogą wystąpić w roli wartości typu A na stosie programu, bo, na przykład, mają inny rozmiar (formalnie jest to możliwe, ale wiąże się z „szatkowaniem” obiektu — na stosie położony zostanie podobiekt klasy A obiektu klasy B, co zazwyczaj nie jest tym czego byśmy chcieli).
Inna jest sytuacja, jeśli, odwrotnie, mamy zmienną typu B*, a przypisać jej chcielibyśmy wartość typu A*. Obiekt wskazywany przez ten wskaźnik może, ale nie musi być obiektem klasy pochodnej B. Konwersja zatem w tę stronę, od wskaźnika na obiekt klasy bazowej do wskaźnika na obiekt klasy pochodnej, może się nie powieść. Musi zatem być dokonana jawnie. Ale kompilator nie jest w stanie stwierdzić, czy takie przekształcenie jest prawidłowe, bo nie wie, jaki będzie typ obiektu wskazywanego podczas wykonania programu. Nie można zatem użyć statycznego operatora konwersji. I właśnie wtedy przydaje się operator konwersji dynamicznej dynamic_cast. Składnia jego użycia jest następująca:
dynamic_cast<Typ>(wyrazenie)Zachodzą dwa przypadki:
1. #include <iostream> 2. #include <string> 3. using namespace std; 4. 5. class Program { 6. protected: 7. string name; 8. public: 9. Program(string n) 10. : name(n) 11. { } 12. virtual void print() = 0; 13. virtual ~Program() { }; 14. }; 15. 16. class Freeware : public Program { 17. public: 18. Freeware(string n) 19. : Program(n) 20. { } 21. void print() { 22. cout << "Free : " << name << endl; 23. } 24. }; 25. 26. class Shareware : public Program { 27. int price; 28. public: 29. Shareware(string n, int c) 30. : Program(n), price(c) 31. { } 32. void print() { 33. cout << "Share: " << name 34. << ", price " << price << endl; 35. } 36. int getPrice() { 37. return price; 38. } 39. }; 40. 41. int total(Program* prgs[], int size) { 42. Shareware* sh; 43. int tot = 0; 44. for (int i = 0; i < size; ++i) { 45. prgs[i]->print(); 46. if ( sh = dynamic_cast<Shareware*>(prgs[i]) ) 47. tot += sh->getPrice(); 48. } 49. return tot; 50. } 51. 52. int main() { 53. Freeware anjuta("Anjuta"); Shareware wz("WinZip",30); 54. Freeware mysql("MySQL"); Shareware rar("RAR",25); 55. 56. Program* prgs[] = { &anjuta, &wz, &mysql, &rar }; 57. 58. int tot = total(prgs, sizeof(prgs)/sizeof(prgs[0])); 59. 60. cout << "\nTotal: $" << tot << endl; 61. }
Wynikiem działania programu jest
Free : Anjuta Share: WinZip, price 30 Free : MySQL Share: RAR, price 25 Total: $55Aby zilustrować drugą możliwość, w powyższym programie moglibyśmy zapisać funkcję total następująco:
1. int total(Program* prgs[], int size) { 2. int tot = 0; 3. for (int i = 0; i < size; ++i) { 4. prgs[i]->print(); 5. try { 6. Shareware& sh = 7. dynamic_cast<Shareware&>(*prgs[i]); 8. tot += sh.getPrice(); 9. } catch(bad_cast) { } 10. } 11. return tot; 12. }Tym razem rzutujemy (linia 7) nie wskaźnik, ale wartość obiektową wskazywaną przez ten wskaźnik: prgs[i] jest wskaźnikiem, więc *prgs[i] jest l-wartością wskazywanego obiektu. Jeśli rzutowanie do typu Shareware& powiedzie się, to odczytujemy i dodajemy cenę. Jeśli nie powiedzie się, to znaczy obiekt nie był klasy pochodnej Shareware, zgłaszany jest wyjątek i sterowanie nie dochodzi do linii 8, tylko wchodzi do frazy catch, gdzie wyjątek ten jest po prostu ignorowany, po czym program przechodzi do wykonania kolejnego obrotu pętli.
T.R. Werner, 21 lutego 2016; 20:17