Dynamic cast c что это
Перейти к содержимому

Dynamic cast c что это

  • автор:

 

Динамическая идентификация типов времени выполнения

Динамическая идентификация типов времени выполнения позволяет программе узнать реальный производный тип объекта, адресуемого по ссылке или по указателю на базовый класс. Реализована 2 операциями:

  • dynamic_cast – поддерживает преобразование типов времени выполнения
  • операция typeid идентифицирует реальный тип выражения

Операция dynamic_cast

Встроенная унарная операция dynamic_cast языка C++ позволяет безопасно трансформировать указатель на базовый класс в указатель на производный класс:

При невозможности трансформации возвращается нулевой указатель

Операция dynamic_cast (ссылки)

То же самое можно проделать и с ссылками на объект.

При невозможности ссылочного преобразования генерируется bad_cast исключение. В общем случае все операции трансформации нужно производить ожидая, что может сгенерироваться ошибка. Работу с исключительными операциями рассмотрим позднее.

Операция typeid

Встроенная унарная операция typeid позволяет установить фактический тип любых объектов

Зачем нужен dynamic_cast?

Для чего нужен dynamic_cast ? Какие преимущества имеет запись 1 по сравнению с записью 2 и какие недостатки? В каких случаях какой вариант следует использовать?

Harry's user avatar

Вообще-то, в том варианте, как вы записали, не нужно приводить ничего, потому что объект производного класса уже ЯВЛЯЕТСЯ объектом базового. Так что можно просто писать

А вот если наоборот —

начинаются проблемы. Потому что обычно это говорит о плохом проектировании. Вы хотите использовать именно потомка там, где используется только предок. Т.е. по сути дописать какое-то хитрое поведение там, где о нем ничего не известно и не должно быть известно.

dynamic_cast позволяет не обрушить всю программу потому только, что вы сделали что-то неверно. Ну, например,

Здесь вы вызываете функцию-член, которая есть у Derived , но которой нет в Base . Но что будет, если вы действительно передадите в функцию указтель на Base ? Указатель на другого потомка Base , который ничего об этой функции не знает?

А dynamic_cast позволяет по крайней мере убедиться, что происходит именно то, что нужно, т.е. что там действительно указатель на Derived .

Что до приведения в стиле C — (A*)b — то тут, пожалуй, ближе всего reinterpret_cast — просто рассматривать биты как имеющие иной тип. Без каких-либо проверок. Что уже опасно, но, по крайней мере, в тексте программы в глаза будет бросаться это длинное слово — reinterpret_cast — как указатель на опасность. И это не шутка, это я повторяю Страуструпа — насчет длинных неуклюжих названий . _cast .

18.10 – Динамическое приведение типов

Еще в уроке «8.5 – Явное преобразование (приведение) типов данных и static_cast » мы изучили концепцию преобразования типов и использование static_cast для преобразования переменных из одного типа в другой.

В этом уроке мы продолжим рассмотрение другого типа приведения: dynamic_cast .

Необходимость в dynamic_cast

Имея дело с полиморфизмом, вы часто сталкиваетесь со случаями, когда у вас есть указатель на базовый класс, но вы хотите получить доступ к какой-либо информации, которая существует только в производном классе.

Рассмотрим следующую (слегка надуманную) программу:

В этой программе функция getObject() всегда возвращает указатель Base , но этот указатель может указывать либо на объект Base , либо на объект Derived . В случае, когда указатель указывает на объект Derived , как нам вызвать Derived::getName() ?

Один из способов – добавить в Base виртуальную функцию с именем getName() (чтобы мы могли вызывать ее с помощью указателя/ссылки Base и динамически разрешать ее вызов в Derived::getName() ). Но что бы эта функция вернула, если бы вы вызвали ее с помощью указателя/ссылки Base , которая на самом деле указывала на объект Base ? У него нет никакого осмысленного значения для возврата. Более того, мы загрязнили бы наш класс Base вещами, которые на самом деле должны быть заботой только класса Derived .

Мы знаем, что C++ позволяет вам неявно преобразовать указатель Derived в указатель Base (фактически, getObject() делает именно это). Этот процесс иногда называют повышающим преобразование (upcasting). Однако что, если бы существовал способ преобразовать указатель Base обратно в указатель Derived ? Тогда мы могли бы вызвать Derived::getName() напрямую, используя этот указатель, и вообще не беспокоиться о добавлении виртуальной функции.

dynamic_cast

C++ предоставляет оператор приведения с именем dynamic_cast , который можно использовать только для этой цели. Хотя динамическое приведение имеет несколько различных возможностей, наиболее распространенное использование динамического приведения – преобразование указателей базового класса в указатели производного класса. Этот процесс называется понижающим преобразованием (downcasting).

 

Использование dynamic_cast работает так же, как static_cast . Вот наш пример main() выше, с использованием dynamic_cast для преобразования нашего указателя Base обратно в указатель Derived :

Этот код печатает:

Сбой dynamic_cast

Приведенный выше пример работает, потому что b фактически указывает на объект Derived , поэтому преобразование b в указатель Derived проходит успешно.

Однако мы сделали довольно опасное предположение, что b указывает на объект Derived . Что, если бы b не указывал на объект Derived ? Это легко проверить, изменив аргумент getObject() с true на false . В этом случае getObject() вернет указатель Base на объект Base . Когда мы попытаемся выполнить динамическое преобразование в объект Derived , это не удастся, потому что это преобразование не может быть выполнено.

Если dynamic_cast завершается неудачей, результатом преобразования будет нулевой указатель.

Поскольку мы не проверили результат на нулевой указатель, мы обращаемся к d->getName() , что пытается разыменовать нулевой указатель, что приводит к неопределенному поведению (возможно, к сбою).

Чтобы сделать эту программу безопасной, нам нужно убедиться, что результат выполнения dynamic_cast действительно успешен:

Правило

Всегда проверяйте результат выполнения динамического приведения, проверяя результат на нулевой указатель.

Обратите внимание: поскольку dynamic_cast во время выполнения выполняет какую-то проверку согласованности (чтобы гарантировать, что преобразование может быть выполнено), использование dynamic_cast приводит к снижению производительности.

Также обратите внимание, что есть несколько случаев, когда понижающее преобразование с использованием dynamic_cast не работает:

  1. с защищенным или закрытым наследованием;
  2. для классов, которые не объявляют и не наследуют какие-либо виртуальные функции (и, следовательно, не имеют виртуальной таблицы);
  3. в некоторых случаях, связанных с виртуальными базовыми классами .

Понижающее преобразование с помощью static_cast

Оказывается, что понижающее преобразование также можно выполнить с помощью static_cast . Основное отличие состоит в том, что static_cast не выполняет проверку типов во время выполнения, чтобы убедиться, что то, что вы делаете, имеет смысл. Это делает использование static_cast более быстрым, но более опасным. Если вы приведете Base* к Derived* , преобразование будет «успешным», даже если указатель Base не указывает на объект Derived . Это приведет к неопределенному поведению, когда вы попытаетесь получить доступ к полученному указателю Derived (который на самом деле указывает на объект Base ).

Если вы абсолютно уверены, что указатель, для которого вы выполняете понижающее преобразование, будет корректным, использование static_cast допустимо. Один из способов убедиться, что вы знаете, на какой тип объекта вы указываете, – использовать виртуальную функцию. Вот один из способов (не самый лучший, потому что в нем используется глобальная переменная):

Но если вы готовы пройти через все эти проблемы, чтобы реализовать это (и заплатить за вызов виртуальной функции и обработку результата), вы можете просто использовать dynamic_cast .

dynamic_cast и ссылки

Хотя во всех приведенных выше примерах показано динамическое приведение указателей (что является более распространенным), dynamic_cast также может использоваться и со ссылками. Это работает аналогично тому, как dynamic_cast работает с указателями.

Поскольку C++ не имеет «нулевой ссылки», dynamic_cast не может возвращать нулевую ссылку в случае ошибки. Вместо этого, если dynamic_cast ссылки терпит неудачу, генерируется исключение типа std::bad_cast . Об исключениях мы поговорим позже в этой серии статей.

dynamic_cast против static_cast

Начинающие программисты иногда не понимают, когда использовать static_cast , а когда dynamic_cast . Ответ довольно прост: используйте static_cast , если только вы не выполняете понижающие преобразование, так как в этом случае dynamic_cast обычно лучше. Однако вам также следует подумать о том, чтобы полностью избежать приведения типов и просто использовать виртуальные функции.

Понижающее преобразование против виртуальных функций

Некоторые разработчики считают, что dynamic_cast – это зло и указывает на плохой дизайн класса. Эти программисты говорят, что вместо него вам следует использовать виртуальные функции.

В общем случае, использование виртуальной функции должно быть предпочтительнее понижающего преобразования. Однако бывают случаи, когда понижающее преобразование является лучшим выбором:

  • когда вы не можете изменить базовый класс для добавления виртуальной функции (например, потому что базовый класс является частью стандартной библиотеки);
  • когда вам нужен доступ к чему-то, специфичному для производного класса (например, функция доступа, которая существует только в производном классе);
  • когда добавлять виртуальную функцию к базовому классу не имеет смысла (например, в базовом классе нет подходящего значения для возврата). Использование чисто виртуальной функции может быть здесь допустимо, если вам не нужно создавать экземпляры базового класса.

Предупреждение о dynamic_cast и RTTI

Динамическая идентификация типа данных (RTTI, run-time type information) – это функция C++, которая предоставляет информацию о типе данных объекта во время выполнения. Эта возможность используется в dynamic_cast . Поскольку RTTI требует значительных затрат производительности, некоторые компиляторы для оптимизации позволяют отключать RTTI. Излишне говорить, что если вы это сделаете, dynamic_cast не будет работать правильно.

dynamic_cast conversion

Safely converts pointers and references to classes up, down, and sideways along the inheritance hierarchy.

Contents

[edit] Syntax

dynamic_cast< new-type >( expression )
new-type pointer to complete class type, reference to complete class type, or pointer to (optionally cv-qualified) void
expression lvalue (until C++11) glvalue (since C++11) of a complete class type if new-type is a reference, prvalue of a pointer to complete class type if new-type is a pointer.

If the cast is successful, dynamic_cast returns a value of type new-type . If the cast fails and new-type is a pointer type, it returns a null pointer of that type. If the cast fails and new-type is a reference type, it throws an exception that matches a handler of type std::bad_cast .

[edit] Explanation

For the convenience of description, » expression or the result is a reference to T » means that «it is a glvalue of type T «, which follows the convention of decltype .

Only the following conversions can be done with dynamic_cast , except when such conversions would cast away constness or volatility.

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *