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

Dynamic cast c что это

Еще раз про приведение типов в языке С++ или расстановка всех точек над cast


Этот пост попытка кратко оформить все, что я читал или слышал из разных источников про операторы приведения типов в языке C++. Информация ориентирована в основном на тех, кто изучает C++ относительно недолго и, как мне кажется, должна помочь понять cпецифику применения данных операторов. Старожилы и гуру С++ возможно помогут дополнить или скорректировать описанную мной картину. Всех интересующихся приглашаю под кат.

Приведение типов в стиле языка C (C-style cast)

Приведение типов в стиле языка C может привести выражение любого типа к любому другому типу данных (исключение это приведение пользовательских типов по значению, если не определены правила их приведения, а также приведение вещественного типа к указателю или наоборот). К примеру, unsigned int может быть преобразован к указателю на double. Данный метод приведения типов может быть использован в языке C++. Однако, метод приведения типов в стиле языка C не делает проверки типов на совместимость, как это могут сделать static_cast и dynamic_cast на этапе компиляции и на этапе выполнения соответственно. При этом все, что умеют const_cast и reinterpret_cast данный метод приведения типов делать может.

Общий вид приведения:

(new_type)exp

, где new_type – новый тип, к которому приводим, а exp – выражение, которое приводится к новому типу.

Т.к. данный оператор не имеет зарезервированного ключевого слова (например, static_cast) найти все места приведения типов в тексте программы будет не очень удобно, если это потребуется.

const_cast

Оператор приведения const_cast удаляет или добавляет квалификаторы const и volatile с исходного типа данных (простые типы, пользовательские типы, указатели, ссылки). Например, был const int, а после преобразования стал int или наоборот. Квалификаторы const и volatile называют cv-квалификаторы (cv-qualifiers). Данные квалификаторы указываются перед именами типов. Как ни трудно догадаться квалификатор const задает константность, т.е. защищает переменную от изменения. Квалификатор volatile говорит о том, что значение переменной может меняться без явного выполнения присваивания. Это обеспечивает защиту от оптимизации компилятором операций с данной переменной.

Общий вид приведения:

const_cast<new_type>(exp)

Дополнительный пример от пользователя 5nw

Квалификаторы const и volatile можно удалить или добавить только с помощью оператора приведения const_cast и приведения типов в стиле языка C. Другие операторы приведения типов не влияют на квалификаторы const и volatile (reinterpret_cast, static_cast, dynamic_cast).

reinterpret_cast

Оператор приведения reinterpret_cast используется для приведения несовместимых типов. Может приводить целое число к указателю, указатель к целому числу, указатель к указателю (это же касается и ссылок). Является функционально усеченным аналогом приведения типов в стиле языка С. Отличие состоит в том, что reinterpret_cast не может снимать квалификаторы const и volatile, а также не может делать небезопасное приведение типов не через указатели, а напрямую по значению. Например, переменную типа int к переменной типа double привести при помощи reinterpret_cast нельзя.

Общий вид приведения:

reinterpret_cast<new_type>(exp)

static_cast

Оператор приведения static_cast применяется для неполиморфного приведения типов на этапе компиляции программы. Отличие static_cast от приведения типов в стиле языка C состоит в том, что данный оператор приведения может отслеживать недопустимые преобразования, такие как приведение указателя к значению или наоборот (unsigned int к указателю на double не приведет), а также приведение указателей и ссылок разных типов считается корректным только, если это приведение вверх или вниз по одной иерархии наследования классов, либо это указатель на void. В случае фиксации отклонения от данных ограничений будет выдана ошибка при компиляции программы. При множественном наследовании static_cast может вернуть указатель не на исходный объект, а на его подобъект.

Общий вид приведения:

static _cast<new_type>(exp)

dynamic_cast

Оператор приведения dynamic_cast применяется для полиморфного приведения типов на этапе выполнения программы (класс считается полиморфным, если в нем есть хотя бы одна виртуальная функция). Если указатель, подлежащий приведению, ссылается на объект результирующего класса или объект класса производный от результирующего то приведение считается успешным. То же самое для ссылок. Если приведение невозможно, то на этапе выполнения программы будет возвращен NULL, если приводятся указатели. Если приведение производится над ссылками, то будет сгенерировано исключение std::bad_cast. Несмотря на то, что dynamic_cast предназначен для приведения полиморфных типов по иерархии наследования, он может быть использован и для обычных неполиморфных типов вверх по иерархии. В этом случае ошибка будет получена на этапе компиляции. Оператор приведения dynamic_cast может приводить указатель на полиморфный тип к указателю на void, но не может приводить указатель на void к другому типу. Способность dynamic_cast приводить полиморфные типы обеспечивается системой RTTI (Run-Time Type Identification), которая позволяет идентифицировать тип объекта в процессе выполнения программы. При множественном наследовании dynamic_cast может вернуть указатель не на исходный объект, а на его подобъект.

Оператор dynamic_cast

Преобразует операнд expression в объект типа type-id .

Синтаксис

Remarks

Параметр type-id должен быть указателем или ссылкой на ранее определенный тип класса или «указателем на void». Тип операнда expression должен быть указателем, если type-id является указателем, или l-значением, если type-id является ссылкой.

Сведения о различиях между статическими и динамическими преобразованиями приведения и их использовании см. в static_cast .

В управляемом dynamic_cast коде есть два критических изменения:

dynamic_cast на указатель к базовому типу упакованного перечисления вызывает сбой во время выполнения; вместо преобразованного указателя будет возвращено значение 0.

dynamic_cast больше не вызывает исключение, если type-id это внутренний указатель на тип значения, при этом приведение завершается сбоем во время выполнения. Приведение теперь возвращает значение указателя 0, а исключение не создается.

Если type-id является указателем на однозначно доступный прямой или косвенный базовый класс операнда expression , то результатом будет указатель на уникальный подобъект типа type-id . Пример:

Этот тип преобразования называется «восходящим приведением типа», поскольку при нем указатель перемещается вверх по иерархии классов: от производного класса к классу, от которого он является производным. Восходящее приведение типа является неявным преобразованием.

Если type-id является указателем void*, выполняется проверка во время выполнения, чтобы определить фактический тип операнда expression . Результатом является указатель на полный объект, на который указывает операнд expression . Пример:

Если type-id не является указателем void*, то выполняется проверка во время выполнения, чтобы определить, может ли объект, на который указывает операнд expression , быть преобразован в тип, указанный параметром type-id .

Если тип операнда expression является базовым классом типа, заданного параметром type-id , то выполняется проверка во время выполнения, чтобы определить, указывает ли операнд expression на полный объект типа, заданного параметром type-id . Если это так, то результатом является указатель на полный объект типа, заданного параметром type-id . Пример:

Этот тип преобразования называется «нисходящим приведением типа», поскольку при нем указатель перемещается вниз по иерархии классов: от заданного класса к производному от него классу.

В случаях множественного наследования возникают возможности для неоднозначности. Рассмотрим для примера иерархию классов, показанную на следующем рисунке.

Если преобразование может выполняться неявно, или инструкция MSIL isinst , dynamic_cast которая выполняет динамическую проверку и возвращает, nullptr если преобразование завершается ошибкой.

Следующий пример используется для dynamic_cast определения того, является ли класс экземпляром определенного типа:

Иерархия классов, показывающая множественное наследование

Указатель на объект типа D можно безопасно привести к B или C . Однако если в результате приведения D указывает на объект A , какой экземпляр объекта A будет являться результатом? Это может привести к ошибке неоднозначного приведения. Чтобы обойти эту проблему, можно выполнить два однозначных приведения. Пример:

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

Иерархия классов, показывающая виртуальные базовые классы

В этой иерархии объект A является виртуальным базовым классом. Учитывая экземпляр класса E и указатель на A подобъект, dynamic_cast указатель на указатель на B сбой из-за неоднозначности. Необходимо сначала выполнить обратное приведение к полному объекту E , затем однозначным образом вернуться вверх по иерархии, чтобы дойти до нужного объекта B .

Рассмотрим для примера иерархию классов, показанную на следующем рисунке.

Иерархия классов, показывающая повторяющиеся базовые классы

При использовании заданного объекта типа E и указателя на подобъект D можно выполнить три преобразования, чтобы перейти от подобъекта D к крайнему слева подобъекту A . Вы можете выполнить dynamic_cast преобразование из указателя E в D указатель, а затем преобразование (либо dynamic_cast неявное преобразование) в E B , а затем неявное преобразование из . A B Пример:

Оператор dynamic_cast также можно использовать для выполнения перекрестного приведения. Используя ту же иерархию классов, можно привести указатель, например, из B подобъекта к подобъекту D , если полный объект имеет тип E .

С учетом перекрестного приведения можно выполнить преобразование из указателя на объект D в крайний левый подобъект A всего за два шага. Можно перекрестное приведение из D в B , а затем неявное преобразование из B в A . Пример:

Значение указателя NULL преобразуется в значение null указателя целевого типа по dynamic_cast .

Если при использовании оператора dynamic_cast < type-id > ( expression ) невозможно точно преобразовать операнд expression в тип type-id , то проверка во время выполнения приводит к сбою приведения. Пример:

Значением приведения к типу указателя, которое привело к сбою, является пустой указатель. Неудачный приведение к ссылочным типам вызывает исключение bad_cast. Если expression не указывает на допустимый объект или не ссылается на нее, __non_rtti_object создается исключение.

Сведения об исключении см. в описании __non_rtti_object typeid.

Пример

В следующем примере создается указатель базового класса (структура A) на объект (структура C). Это (а также тот факт, что они являются виртуальными функциями) порождает полиморфизм времени выполнения.

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

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

user avatar

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

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

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

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

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

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

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

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

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