Динамическое приведение типов, в отличие от статического, выполняется во время выполнения программы. Для этого применяется функция dynamic_cast<>().
Также как и для static_cast
, в угловых скобках указывается тип, к которому выполняется преобразование, в круглые скобки передается преобразуемый объект:
dynamic_cast<тип_в_который_преобразуем>(преобразуемый_объект)
Но эту функцию можно применять только к указателям и ссылкам на полиморфные типы классов, которые содержат хотя бы одну виртуальную функцию.
Причина в том, что только указатели на типы полиморфных классов содержат информацию, которая необходима функции dynamic_cast
для проверки правильности преобразования. Конечно, типы, между которыми выполняется преобразование, должны быть указателями или ссылками на классы в одной иерархии классов.
Есть два вида динамического приведения. Первый — это преобразование от указателя на базовый класс к указателю на производный класс - так называемое нисхолящее преобразование или downcast (базовые классы в иерархии помещаются вверху, а производные внизу, поэтому преобразование идет сверху вниз). Второй тип — преобразование между базовыми типами в одной иерархии (при множественном наследовании) - кросскаст (crosscast).
Рассмотрим следующую программу:
#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 // динамическое преобразование из Book в Ebook Ebook* ebook{dynamic_cast<Ebook*>(book)}; ebook->print(); // About C++ Pages: 350 Size: 6Mb }
Здесь у нас есть класс Book, который представляет книгу с переменными title и pages для хранения названия книги и количества страниц. А также есть класс File, который представляет электронный файл, в котором для хранения размера определено поле size. Класс электронной книги Ebook наследуется от обоих классов.
Чтобы динамическое преобразование было возможно, базовые классы определяют виртуальную функцию print.
В функции main создаем один объект типа Ebook и его адрес передаем указателю book, который представляет базовый тип Book*. Поскольку этот указатель все таки хранит адрес объекта Ebook, то мы можем привести его к указателю на Ebook:
Ebook* ebook{dynamic_cast<Ebook*>(book)}; ebook->print(); // About C++ Pages: 350 Size: 6Mb
Далее через указатель мы сможем обращаться к функционалу класса Ebook.
Стоит отметить, что в данном случае динамическое преобразование не имеет смысла, так как мы итак могли бы вызвать у указателя book функцию print и за счет виртуальности функции получили бы тот же самый результат. Преобразование нужно, если нам необходимо обратиться к каким-то членам производного класса, которые не определены в базовом. Например, класс Book не имеет функции getSize(), и чтобы обратиться к ней могло потребоваться преобразование.
В примере выше был приведен так называемый downcast. Теперь рассмотрим crosscast:
int main() { Ebook cppbook{"About C++", 350, 6}; Book* book = &cppbook; // указывает на объект Ebook // динамическое преобразование из Book в File - crosscast File* file{dynamic_cast<File*>(book)}; file->print(); // About C++ Pages: 350 Size: 6Mb }
Преобразование из указателя на Book в указатель на File является кросскастом и в данном случае возможно, потому что указатель book хранит адрес объекта Ebook, который также наследуется от File.
Но подобные преобразования не всегда выполняются успешно. В этом случае функция dynamic_cast()
возвращает указатель nullptr, и после получения результата
мы можем проверить на это значение:
int main() { Book cppbook{"About C++", 350}; Book* book = &cppbook; // указывает на объект Book // динамическое преобразование из Book в File - crosscast File* file{dynamic_cast<File*>(book)}; // проверяем результат if(file) // если file !=nullptr { file->print(); } else { std::cout << "The book is not a file" << std::endl; } }
В данном случае указатель book хранит адрес объекта Book и поэтому не может быть преобразован к типу указателя на File. Поэтому вызов dynamic_cast<File*>(book)
возвратит nullptr. После этого мы можем проверить результат и в зависимости от результата проверки выполнить опреленные действия.
Обратите внимание, что если преобразуемый указатель является указателем на константу, то тип указателя, к которому выполняется приведение, также должен представлять указатель на константу:
int main() { const Ebook cppbook{"About C++", 350, 6}; const Book* book = &cppbook; // указатель на константу // преобразование в указатель на константу const Ebook* file{dynamic_cast<const Ebook*>(book)}; file->print(); }
Если необходимо выполнить приведение из указателя на константу в обычный указатель (не на константу), то сначала надо выполнить приведение к указателю того же типа, что и исходный, с помощью функции const_cast<T>():
int main() { const Ebook cppbook{"About C++", 350, 6}; const Book* const_book = &cppbook; // указатель на константу // преобразование из указателя на константу в обычный указатель того же типа Book* book {const_cast<Book*>(const_book)}; // преобразуем указатели Ebook* file{dynamic_cast<Ebook*>(book)}; file->print(); }
Функция dynamic_cast
также может применяться к ссылкам (из ссылки на базовый тип в ссылку на производный тип):
int main() { Ebook cppbook{"About C++", 350, 6}; Book& book {cppbook}; // ссылка на Ebook // преобразуем в ссылку на Ebook Ebook& file{dynamic_cast<Ebook&>(book)}; file.print(); }
В данном случае ссылка book в реальности ссылается на объект Ebook, поэтому эту ссылку можно преобразовать в ссылку Ebook&. Но что, если ссылку book не ссылается на объект Ebook:
int main() { Book cppbook{"About C++", 350}; Book& book {cppbook}; // ссылка на Book // преобразуем в ссылку на Ebook Ebook& file{dynamic_cast<Ebook&>(book)}; // ! Ошибка std::bad_cast file.print(); }
В этом случае при преобразовании мы столкнемся с ошибкой, и программа завершит свое выполнение. Если с указателями мы могли бы проверить результат на nullptr, то в случае с ссылками мы этого сделать не можем. Однако, чтобы избежать некорректного преобразования ссылок мы опять же можем преобразовывать в соответствующий тип указателя
int main() { Book cppbook{"About C++", 350}; Book& book {cppbook}; // ссылка на Book // преобразуем в ссылку на указатель на Ebook Ebook* file{dynamic_cast<Ebook*>(&book)}; if(file) file->print(); else std::cout << "Object is not a file" << std::endl; }
Для динамического преобразования смарт-указателей std::shared_ptr
применяется функция std::dynamic_pointer_cast<T>():
int main() { // указатель std::shared_ptr<Book> указывает на объект Ebook std::shared_ptr<Book> book{std::make_shared<Ebook>("About C++", 350, 6)}; // динамическое преобразование из Book в Ebook std::shared_ptr<Ebook> ebook{std::dynamic_pointer_cast<Ebook>(book)}; ebook->print(); // About C++ Pages: 350 Size: 6Mb }
В данном случае указатель book, который представляет тип std::shared_ptr<Book>
в реальности указывает на объект Ebook. Поэтому его можно привести к типу
указателя std::shared_ptr<Ebook>
. Если же преобразование невозможно, то функция возвращает nullptr
:
int main() { // указатель std::shared_ptr<Book> указывает на объект Ebook std::shared_ptr<Book> book{std::make_shared<Book>("About Java", 280)}; // динамическое преобразование из Book в Ebook std::shared_ptr<Ebook> ebook{std::dynamic_pointer_cast<Ebook>(book)}; if(ebook) // if (ebook != nullptr) { ebook->print(); } else { std::cout << "Object is not e-book" << std::endl; } }
В данном случае указатель book указывает на объект Book. Поэтому при преобразовании в указатель на объект Ebook функция возвратит nullptr
.