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 – выражение, которое приводится к новому типу.
Т.к. данный оператор не имеет зарезервированного ключевого слова (например, static_cast) найти все места приведения типов в тексте программы будет не очень удобно, если это потребуется.
const_cast
Оператор приведения const_cast удаляет или добавляет квалификаторы const и volatile с исходного типа данных (простые типы, пользовательские типы, указатели, ссылки). Например, был const int, а после преобразования стал int или наоборот. Квалификаторы const и volatile называют cv-квалификаторы (cv-qualifiers). Данные квалификаторы указываются перед именами типов. Как ни трудно догадаться квалификатор const задает константность, т.е. защищает переменную от изменения. Квалификатор volatile говорит о том, что значение переменной может меняться без явного выполнения присваивания. Это обеспечивает защиту от оптимизации компилятором операций с данной переменной.
Общий вид приведения:
Дополнительный пример от пользователя 5nw
reinterpret_cast
Оператор приведения reinterpret_cast используется для приведения несовместимых типов. Может приводить целое число к указателю, указатель к целому числу, указатель к указателю (это же касается и ссылок). Является функционально усеченным аналогом приведения типов в стиле языка С. Отличие состоит в том, что reinterpret_cast не может снимать квалификаторы const и volatile, а также не может делать небезопасное приведение типов не через указатели, а напрямую по значению. Например, переменную типа int к переменной типа double привести при помощи reinterpret_cast нельзя.
Общий вид приведения:
static_cast
Оператор приведения static_cast применяется для неполиморфного приведения типов на этапе компиляции программы. Отличие static_cast от приведения типов в стиле языка C состоит в том, что данный оператор приведения может отслеживать недопустимые преобразования, такие как приведение указателя к значению или наоборот (unsigned int к указателю на double не приведет), а также приведение указателей и ссылок разных типов считается корректным только, если это приведение вверх или вниз по одной иерархии наследования классов, либо это указатель на void. В случае фиксации отклонения от данных ограничений будет выдана ошибка при компиляции программы. При множественном наследовании static_cast может вернуть указатель не на исходный объект, а на его подобъект.
Общий вид приведения:
dynamic_cast
Оператор приведения dynamic_cast применяется для полиморфного приведения типов на этапе выполнения программы (класс считается полиморфным, если в нем есть хотя бы одна виртуальная функция). Если указатель, подлежащий приведению, ссылается на объект результирующего класса или объект класса производный от результирующего то приведение считается успешным. То же самое для ссылок. Если приведение невозможно, то на этапе выполнения программы будет возвращен NULL, если приводятся указатели. Если приведение производится над ссылками, то будет сгенерировано исключение std::bad_cast. Несмотря на то, что dynamic_cast предназначен для приведения полиморфных типов по иерархии наследования, он может быть использован и для обычных неполиморфных типов вверх по иерахии. В этом случае ошибка будет получена на этапе компиляции. Оператор приведения dynamic_cast приводить к указателю на void, но не может приводить указатель на void к другому типу. Способность dynamic_cast приводить полиморфные типы обеспечивается системой RTTI (Run-Time Type Identification), которая позволяет идентифицировать тип объекта в процессе выполнения программы. При множественном наследовании dynamic_cast может вернуть указатель не на исходный объект, а на его подобъект.
Приведение типов. Наглядное отличие static_cast от dynamic_cast
Доброго времени суток. Очень много статей в интернете о разнице операторов приведения типов, но понимания в данной теме они мне не особо то и не добавили. Пришлось разбираться самому. Хочу поделиться с вами моим опытом на довольно наглядном примере.
Статья рассчитана на тех, кто хочет осознать приведение типов в С++.
Итак, пусть у нас есть такая иерархия наследования:
На картинке изображена иерархия наследования и расположение членов-данных наследников в памяти
Небольшое отступление: почему так важно преобразование типов? Говоря по рабоче-крестьянски, при присваивании объекту типа X объект типа Y, мы должны определить, какое значение будет иметь после присваивания объект типа X.
Начнем с использования static_cast:
Почему таков эффект при выводе значений указателей (значение указателя это адрес, по которому лежит переменная)? Дело в том, что static_cast производит сдвиг указателя.
Рассмотрим на примере:
1. Происходит преобразование типа из C* в D*. Результатом этого есть указатель типа D* (назовем его tempD), который указывает (внимание!) на ту часть в объекте класса C, которая унаследована от класса D. Значение самого pC не меняется!
2. Теперь присваиваем указателю pD значение указателя tempD (всё хорошо, типы одинаковы)
Разумный вопрос: а зачем собственно нужно сдвигать указатель? Говоря по простому, указатель класса D* руководствуется определением класса D. Если бы не произошло смещения, то меняя значения переменных через указатель D, мы бы меняли переменные объекта класса С, которые не относятся к переменным, унаследованным от класса D (если бы указатель pD имел то же значение, что и pC, то при обращении pD->f в действительности мы бы работали с переменной
а).
Промежуточный итог: static_cast при работе с иерархией классов определяет значения указателей так, чтобы обращение к переменным класса через указатель было корректным.
Поговорим о недостатках static_cast. Вернемся к той же иерархии наследования.
Рассмотрим такой код:
Почему pC->f имеет значение отличное от 0? Рассмотрим код по строчкам:
Теперь если мы хотим сделать запись в переменную g через указатель pB (ведь pB полностью уверен что указывает на объект типа B), мы на самом деле запишем данные в переменную f, унаследованную от класса D. Причем указатель pD будет интерпретировать информацию, записанную в переменную f, как float, что мы и видим при выводе через cout.
Как решить такую проблему?
Для этого следует использовать dynamic_cast, который проверяет не только валидность иерархии классов, но и тот факт, что указатель действительно указывает на объект того типа, к которому мы хотим привести.
Для того, чтобы такая проверка была возможна, следует добавить к классам виртуальность (dynamic_cast использует таблицы виртуальных функций, чтобы делать проверку).
Демонстрация решения проблемы, при той же иерархии классов:
Предлагаю запустить код и убедиться, что операция
не получится (потому что pA указывает на объект типа С, что и проверил dynamic_cast и вынес свой вердикт).
Ссылок никаких не привожу, источник — личный опыт.
dynamic_cast на этапе компиляции
Приветствую все читающих.
О чём статья (или задача статьи): практический ответ на вопрос «возможно ли создать большой проект так, чтобы полностью отказаться от dynamic_cast на этапе выполнения?», где под большим проектом подразумевает такой в котором уже нет человека, что бы держал в голове всю кодовую базу проекта целиком.
Предварительный ответ: ДА, это возможно — возможно создать механизм, позволяющий решить задачу dynamic_cast на этапе компиляции, но — едва ли подобное будет применяться на практике по причинам как: (1) с самого начала целевой проект должен строиться согласно наперёд заданным правилам, в следствии чего взять и применить методику к существующему проекту, очень трудоёмко (2) существенное повышение сложности кода с точки зрения удобства его читаемости в определённых местах, где, собственно, происходит замена логики dynamic_cast на предложенную ниже (3) использование шаблонов, что может быть неприемлемым в некоторых проектах по идеологическим соображениям ответственных за него (4) интерес автора исключительно в том, чтобы дать ответ на поставленный вопрос, а не в том, чтобы создать универсальный и удобный механизм решения поставленной задачи (в конце-концов, не нужно на практике решать проблемы, которые не являются насущными).
Идея реализации
За основу была взята идея списка типов, описанная Андреем Александреску и реализованная им же в библиотеке Loki. Данная идея была доработана по следующим пунктам (пункты помеченные * означают, что по данному пункту автор статьи не согласен с видением Александреску по поводу списков типов):
Для начала будет представлен весь тестовый код с демонстрацией решения поставленной задачи, а далее, один за одним будут представлены и объяснены фрагменты из этого кода, последовательное ознакомление с которыми должно прояснить задумку автора.
Создание первого класса в иерархии class A
Создание второго класса в иерархии class B
Как видим, класс В наследуется от класса А, а потому в его BASE_t — списке типов к которым может быть приведёт класс В, содержится класс А и все базовые типы класса А.
Создание третьего класса в иерархии class C
Класс С, является наследником класса В, следовательно в его BASE_t содержится класс В, и все базовые типы класса В.
Создание четвёртого класса в иерархии class D
Аналогично предыдущему классу, класс D наследуется от класса С и его BASE_t содержит класс С и все его базовые типы.
Создание структуры с информацией о наследовании вверх по иерархии
Создание функции для создания структуры T2T
Шаблонные параметры функции T2TMake имеют следующее предназначение:
По придуманным условиям, данная функция может работать с объектами класса А, являющимися приведёнными от объектов не ниже класса С по иерархии типов, причём, — и это самое важное, — данное условие проверяется ещё на этапе компиляции.
Итоговое тестирование созданной логики
Выводы
dynamic_cast на этапе компиляции — это реально.
Однако, вопрос о целесообразности остаётся насущным.
Спасибо всем, кто прочитал статью 🙂 — буду рад узнать ваш опыт, мнение по, или, возможно, даже решение описанной в статье задачи в комментариях.
Приведение типов
Будучи на конференции Qt Developer Days 2010 я узнал, что одним из самых популярных вопросов на собеседовании в разные зарубежные компании, работающие с Qt библиотекой, является вопрос о различиях в способах приведения типов в C++. Поэтому здесь я рассмотрю основные различия между static_cast, dynamic_cast, const_cast, reinterpret_cast, C-style cast, qobject_cast и qvariant_cast
static_cast преобразует выражения одного статического типа в объекты и значения другого статического типа. Поддерживается преобразование численных типов, указателей и ссылок по иерархии наследования как вверх, так и вниз. Проверка производится на уровне компиляции, так что в случае ошибки сообщение будет получено в момент сборки приложения или библиотеки.
Используется для динамического приведения типов во время выполнения. В случае неправильного приведения типов для ссылок вызывается исключительная ситуация std::bad_cast, а для указателей будет возвращен 0. Использует систему RTTI (Runtime Type Information). Безопасное приведение типов по иерархии наследования, в том числе для виртуального наследования.
Пожалуй самое простое приведение типов. Снимает cv qualifiers — const и volatile, то есть константность и отказ от оптимизации компилятором переменной. Это преобразование проверяется на уровне компиляции и в случае ошибки приведения типов будет выдано сообщение.
Приведение типов без проверки. reinterpret_cast — непосредственное указание компилятору. Применяется только в случае полной уверенности программиста в собственных действиях. Не снимает константность и volatile. применяется для приведения указателя к указателю, указателя к целому и наоборот.
Си-шный метод приведения типов. Пожалуй самый нежелательный способ приведения типов. Страуструп пишет:
«Например, что это значит выражение — x = (T)y;. Мы не знаем. Это зависит от типа T, типов x и y. T может быть названием типа, typedef или может быть параметр template-а. Может быть, х и у являются скалярными переменными и Т представляет собой значение преобразования. Может быть, х объекта класса, производного от класса Y и Т — нисходящее преобразование. По этой причине программист может не знать, что он делает на самом деле.»
Вторая причина нежелательного использования приведения типов в C-style — трудоемкость процесса поиска мест приведения типов.
Приводит объект QObject* к типу TYPE если объект типа объекта TYPE или тип наследует от TYPE иначе возвращает 0. qobject_cast от 0 также дает 0. Необходимое условие. Класс должен наследовать от QObject и содержать в себе макрос Q_OBJECT. Функция ведет себя аналогично стандартному dynamic_cast, но при этом не использует RTTI. Вот как описана данная функция в Qt 4.7.0:
Итак, что тут происходит:
Во-первых если не определены QT_NO_MEMBER_TEMPLATES (определяется только в том случае, если используется версия Microsoft Visual Studio ниже 2002) и QT_NO_QOBJECT_CHECK (определяется в случае использования версии Microsoft Visual Studio ниже 2003), то происходит проверка наличия макроса Q_OBJECT в объявлении класса. И после этого выполняется непосредственно само преобразование — сначала получаем статический объект класса QMetaObject, который называется staticMetaObject, у которого вызывается метод cast, который возвращает const_cast переданного ему объекта, попутно проверяя наследуется ли данный объект от QObject. Далее полученному объекту делается static_cast и возвращается результат.
Приводит объект класса QVariant к нужному классу. Функция аналогична функции qVariantValue.
Рассмотрим, что происходит внутри:
В первой секции кода производится получение идентификатора класса через метасистему Qt. В том случае если класс не зарегистрирован через Q_DECLARE_METATYPE, компиляция кода с приведением к этому типу выдаст ошибку. Далее, если тип объекта, полученный от метасистемы совпадает с типом в значении QVariant, производится reinterpret_cast содержимого объекта, если идентификатор класса не является встроенным типом и его id не совпадает с заложенным в значении QVariant, то возвращается TYPE(). Для случаев, когда мы приводим к встроенному типу, вызывается функция qvariant_cast_helper, которая вызывает в свою очередь функцию convert, адрес которой хранится в структуре Handler. В ней уже осуществляется приведение способом подходящим для типа TYPE. Если конвертация не удалась возвращается объект TYPE()
UPD: Спасибо BaJlepa:
1. const_cast также умеет добавлять cv-квалификаторы
2. для преобразования указателей лучше использовать двойной static_cast через void* вместо reinterpret_cast, потому как такое преобразование позволяет быть уверенным в том, что только pointer-ы участвуют в приведении
Урок №171. Динамическое приведение типов. Оператор dynamic_cast
Обновл. 15 Сен 2021 |
На уроке о явном преобразовании типов данных мы рассматривали использование оператора static_cast для конвертации переменных из одного типа данных в другой. На этом уроке мы рассмотрим еще один оператор явного преобразования — dynamic_cast.
Зачем нужен dynamic_cast?
Применяя полиморфизм на практике вы часто будете сталкиваться с ситуациями, когда у вас есть указатель на родительский класс, но вам нужно получить доступ к данным, которые есть только в дочернем классе. Например:
В этой программе метод getObject() всегда возвращает указатель класса Parent, но этот указатель может указывать либо на объект класса Parent, либо на объект класса Child. В случае, когда указатель указывает на объект класса Child, как мы будем вызывать Child::getName()?
Один из способов — добавить виртуальную функцию getName() в класс Parent (чтобы иметь возможность вызывать переопределение через объект класса Parent). Но, используя этот вариант, мы будем загромождать класс Parent тем, что должно быть заботой только класса Child.
Язык C++ позволяет нам неявно конвертировать указатель класса Child в указатель класса Parent (фактически, это и делает getObject()). Эта конвертация называется приведением к базовому типу (или «повышающим приведением типа»). Однако, что, если бы мы могли конвертировать указатель класса Parent обратно в указатель класса Child? Таким образом, мы могли бы напрямую вызывать Child::getName(), используя тот же указатель, и вообще не заморачиваться с виртуальными функциями.
Оператор dynamic_cast
В языке C++ оператор dynamic_cast используется именно для этого. Хотя динамическое приведение позволяет выполнять не только конвертацию указателей родительского класса в указатели дочернего класса, это является наиболее распространенным применением оператора dynamic_cast. Этот процесс называется приведением к дочернему типу (или «понижающим приведением типа»).
Использование dynamic_cast почти идентично использованию static_cast. Вот функция main() из вышеприведенного примера, где мы используем dynamic_cast для конвертации указателя класса Parent обратно в указатель класса Child: