Conversie dinamică
Conversia dinamică, spre deosebire de conversia statică, se realizează în timpul execuției programului. Pentru aceasta se folosește funcția dynamic_cast<>(). La fel ca în cazul static_cast, în parantezele unghiulare se indică tipul în care dorim să convertim, iar în paranteze rotunde se transmite obiectul de convertit:
dynamic_cast<tip_în_care_convertim>(obiect_de_convertit)
Însă această funcție poate fi aplicată doar asupra pointerilor și referințelor la tipuri polimorfe, adică la clase care conțin cel puțin o funcție virtuală. Motivul este că doar pointerii la clase polimorfe conțin informațiile necesare pentru ca dynamic_cast să verifice corectitudinea conversiei. Desigur, tipurile între care se face conversia trebuie să aparțină aceleiași ierarhii de clase.
Există două tipuri de conversii dinamice. Prima este conversia de la un pointer la clasa de bază către un pointer la clasa derivată – așa-numita conversie descendentă (downcast). A doua este conversia între tipuri de bază dintr-o ierarhie (în caz de moștenire multiplă) – crosscast.
Să analizăm următorul program:
#include <iostream>
class Book
{
public:
Book(std::string title, unsigned pages): title{title}, pages{pages} {}
std::string getTitle() const { return title; }
unsigned getPages() const { return pages; }
virtual void print() const
{
std::cout << title << ". Pages: " << pages << std::endl;
}
private:
std::string title;
unsigned pages;
};
class File
{
public:
File(unsigned size): size{size} {}
unsigned getSize() const { return size; }
virtual void print() const
{
std::cout << "Size: " << size << std::endl;
}
private:
unsigned size;
};
class Ebook : public Book, public File
{
public:
Ebook(std::string title, unsigned pages, unsigned size): Book{title, pages}, File{size} {}
void print() const override
{
std::cout << getTitle() << "\tPages: " << getPages() << "\tSize: " << getSize() << "Mb" << std::endl;
}
};
int main()
{
Ebook cppbook{"About C++", 350, 6};
Book* book = &cppbook;
Ebook* ebook{dynamic_cast<Ebook*>(book)};
ebook->print(); // About C++ Pages: 350 Size: 6Mb
}
Aici avem clasa Book, care reprezintă o carte cu variabilele title și pages, și clasa File, care reprezintă un fișier electronic cu câmpul size. Clasa Ebook moștenește ambele clase.
Pentru ca conversia dinamică să fie posibilă, clasele de bază definesc funcția virtuală print.
În main creăm un obiect de tip Ebook, iar adresa lui este atribuită unui pointer Book*. Deoarece acest pointer stochează adresa unui obiect Ebook, putem face conversia:
Ebook* ebook{dynamic_cast<Ebook*>(book)};
ebook->print();
După aceasta, putem accesa funcționalitățile clasei Ebook.
Trebuie remarcat că în acest caz conversia dinamică nu este neapărat necesară, deoarece apelul book->print() ar fi invocat oricum implementarea din Ebook, datorită funcției virtuale. Conversia devine necesară atunci când vrem să accesăm membri ai clasei derivate care nu sunt definiți în clasa de bază – de exemplu, getSize() nu există în Book.
Exemplul de mai sus este un downcast. Să vedem acum un crosscast:
int main()
{
Ebook cppbook{"About C++", 350, 6};
Book* book = &cppbook;
File* file{dynamic_cast<File*>(book)};
file->print();
}
Conversia din Book* în File* este un crosscast și funcționează aici pentru că book pointează către un obiect Ebook, care moștenește și File.
Totuși, astfel de conversii nu reușesc întotdeauna. Dacă conversia nu este validă, dynamic_cast() returnează nullptr. Putem verifica acest lucru:
int main()
{
Book cppbook{"About C++", 350};
Book* book = &cppbook;
File* file{dynamic_cast<File*>(book)};
if (file)
{
file->print();
}
else
{
std::cout << "The book is not a file" << std::endl;
}
}
În acest caz, pointerul book stochează adresa unui obiect de tip Book, prin urmare nu poate fi convertit într-un pointer de tip File. Astfel, apelul dynamic_cast<File*>(book) va returna nullptr. După aceasta putem verifica rezultatul și, în funcție de rezultat, executăm acțiunile corespunzătoare.
Constantă
Acordați atenție faptului că, dacă pointerul convertit este un pointer la constantă, atunci și tipul în care se face conversia trebuie să fie un pointer la constantă:
int main()
{
const Ebook cppbook{"About C++", 350, 6};
const Book* book = &cppbook; // pointer la constantă
// conversie într-un pointer la constantă
const Ebook* file{dynamic_cast<const Ebook*>(book)};
file->print();
}
Dacă este necesară conversia dintr-un pointer la constantă într-un pointer obișnuit (non-const), mai întâi trebuie făcută conversia către un pointer de același tip folosind funcția const_cast<T>():
int main()
{
const Ebook cppbook{"About C++", 350, 6};
const Book* const_book = &cppbook; // pointer la constantă
// conversie din pointer constant într-un pointer normal de același tip
Book* book {const_cast<Book*>(const_book)};
// convertim pointerul
Ebook* file{dynamic_cast<Ebook*>(book)};
file->print();
}
Conversia referințelor
Funcția dynamic_cast poate fi aplicată și referințelor (din referință la tip de bază în referință la tip derivat):
int main()
{
Ebook cppbook{"About C++", 350, 6};
Book& book {cppbook}; // referință la Ebook
// conversie într-o referință la Ebook
Ebook& file{dynamic_cast<Ebook&>(book)};
file.print();
}
În acest caz, referința book se referă de fapt la un obiect Ebook, așa că poate fi convertită într-o referință Ebook&. Dar ce se întâmplă dacă referința book nu se referă la un obiect Ebook:
int main()
{
Book cppbook{"About C++", 350};
Book& book {cppbook}; // referință la Book
// conversie într-o referință la Ebook
Ebook& file{dynamic_cast<Ebook&>(book)}; // ! Eroare std::bad_cast
file.print();
}
În acest caz, conversia va produce o eroare, iar programul se va termina. Dacă lucrăm cu pointeri, putem verifica rezultatul pentru nullptr, dar în cazul referințelor acest lucru nu este posibil. Totuși, pentru a evita o conversie incorectă a referințelor, putem face mai întâi conversia către pointer:
int main()
{
Book cppbook{"About C++", 350};
Book& book {cppbook}; // referință la Book
// conversie către pointer la Ebook
Ebook* file{dynamic_cast<Ebook*>(&book)};
if(file)
file->print();
else
std::cout << "Object is not a file" << std::endl;
}
Conversia smart-pointerilor
Pentru conversia dinamică a smart-pointerilor std::shared_ptr se folosește funcția std::dynamic_pointer_cast<T>():
int main()
{
// pointer std::shared_ptr<Book> indică spre un obiect Ebook
std::shared_ptr<Book> book{std::make_shared<Ebook>("About C++", 350, 6)};
// conversie dinamică din Book în Ebook
std::shared_ptr<Ebook> ebook{std::dynamic_pointer_cast<Ebook>(book)};
ebook->print(); // About C++ Pages: 350 Size: 6Mb
}
În acest caz, pointerul book, de tip std::shared_ptr<Book>, indică în realitate un obiect Ebook. Prin urmare, el poate fi convertit într-un pointer de tip std::shared_ptr<Ebook>. Dacă însă conversia nu este posibilă, funcția va returna nullptr:
int main()
{
// pointer std::shared_ptr<Book> indică spre un obiect Book
std::shared_ptr<Book> book{std::make_shared<Book>("About Java", 280)};
// conversie dinamică din Book în Ebook
std::shared_ptr<Ebook> ebook{std::dynamic_pointer_cast<Ebook>(book)};
if(ebook) // dacă ebook != nullptr
{
ebook->print();
}
else
{
std::cout << "Object is not e-book" << std::endl;
}
}
În acest caz, pointerul book indică spre un obiect Book. Prin urmare, la conversia către Ebook, funcția returnează nullptr.